vibe-now 1.0.7 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "WebSearch",
5
+ "WebFetch(domain:tanstack.com)",
6
+ "WebFetch(domain:docs.convex.dev)",
7
+ "WebFetch(domain:www.anthropic.com)",
8
+ "WebFetch(domain:github.com)",
9
+ "WebFetch(domain:www.humanlayer.dev)",
10
+ "WebFetch(domain:ui.shadcn.com)",
11
+ "WebFetch(domain:dometrain.com)",
12
+ "Bash(npm link:*)",
13
+ "Bash(node:*)",
14
+ "Bash(chmod +x:*)",
15
+ "Bash(bash:*)",
16
+ "Bash(kill:*)",
17
+ "Bash(rm:*)",
18
+ "Bash(yes:*)",
19
+ "Bash(printf test-1\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n:*)",
20
+ "Bash(printf test-1\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n:*)",
21
+ "Bash(git commit:*)",
22
+ "Bash(git pull:*)",
23
+ "Bash(git:*)",
24
+ "Bash(npm publish:*)"
25
+ ]
26
+ }
27
+ }
package/PLAN.md CHANGED
@@ -1,54 +1,155 @@
1
- # Custom CLI Wizard Plan (Plop.js + Next.js Stack)
2
-
3
- This project has been successfully implemented and released as **Vibe Now**.
4
-
5
- ## 1. Project Overview
6
- A custom CLI (`vibe-now`) that:
7
- - Prompts for project name with validation.
8
- - Dynamically generates interactive prompts group by category.
9
- - Orchestrates project creation and dependency installation using a configuration-driven approach.
10
- - Generates dynamic documentation (AGENTS.md and README.md).
11
-
12
- ## 2. Core Dependencies
13
- - **plop**: Generator framework.
14
- - **inquirer**: Interactive prompts.
15
- - **execa**: Process execution.
16
- - **ora**: Premium progress spinners.
17
- - **handlebars**: Template engine for file generation.
18
-
19
- ## 3. Configuration-Driven Architecture (DRY & Scalable)
20
- The CLI uses a `PACKAGE_GROUPS` array in `lib/packages.js`. Adding a new package or category is fully decoupled from the core logic.
21
-
22
- ## 4. Implementation Details & Commands
23
-
24
- ### Base Next.js Setup
25
- - **Command**: `npx create-next-app@latest`
26
-
27
- ### Features Implemented
28
- - **AI SDK**: Vercel AI SDK & OpenRouter.
29
- - **Linters**: ESLint/Prettier and Biome selection.
30
- - **UI & Helpers**: shadcn/ui, nuqs, hook-form, dayjs, lodash.
31
- - **Database**: Supabase & Drizzle ORM.
32
- - **Payments**: Stripe & Polar.sh.
33
- - **Authentication**: Better Auth.
34
-
35
- ## 5. Implementation Status
36
-
37
- - [x] **Phase 1: Environment Setup** - Completed with ESM support and binary linking.
38
- - [x] **Phase 2: Configuration Hub** - Scalable group-based configuration implemented.
39
- - [x] **Phase 3: CLI Entry Point** - `index.js` and `plopfile.js` logic finalized.
40
- - [x] **Phase 4: Premium UI** - **Ora** spinners integrated for all major steps.
41
- - [x] **Phase 5: Smart Validation** - URL-safe project names and empty directory checks.
42
- - [x] **Phase 6: Dynamic Documentation** - Handlebars-based `README.md` and `AGENTS.md` generation.
43
-
44
- ## 6. Project Structure
45
- ```text
1
+ # Vibe Now v2 Plan
2
+
3
+ ## Overview
4
+
5
+ Four new features: framework choice (Next.js vs TanStack Start), AI editor context (CLAUDE.md vs AGENTS.md), database provider selection (Supabase vs Convex), and auto-generated `.env.example` files.
6
+
7
+ ## Implementation Status
8
+
9
+ - [x] **Feature 1: Framework Selection** Next.js vs TanStack Start
10
+ - [x] **Feature 2: Claude Code Repo** — CLAUDE.md vs AGENTS.md
11
+ - [x] **Feature 3: Database Provider** — Supabase+Drizzle vs Convex (Cloud/Self-hosted)
12
+ - [x] **Feature 4: .env.example Generation** — Auto-generated from selected packages
13
+
14
+ ---
15
+
16
+ ## Feature 1: Framework Selection (Next.js vs TanStack Start) — DONE
17
+
18
+ **Prompt**: List choice, asked after project name.
19
+
20
+ **Files changed:**
21
+ - `plopfile.js` — New `framework` list prompt, conditional scaffold command (`create-next-app` vs `@tanstack/cli create`), `when` callbacks on confirm prompts to filter by framework, `devInstallNextjs` merging for ESLint, `isNextjs`/`isTanStack` template flags
22
+ - `lib/packages.js` Added `frameworks` field to `next-themes` (nextjs-only), `nuqs` (nextjs-only); added new `tanstack-theme-kit` package (tanstack-only); moved `eslint-config-next` into `devInstallNextjs` field
23
+ - `templates/AGENTS.md.hbs` — Conditional architecture overview, directory structure (App Router vs TanStack routes), coding rules, state/data patterns, SEO guidance
24
+ - `templates/CLAUDE.md.hbs` — Same conditional sections
25
+ - `templates/README.md.hbs` — Conditional core framework listing
26
+
27
+ ### Package compatibility matrix
28
+
29
+ | Package | Next.js | TanStack Start | Notes |
30
+ |---|---|---|---|
31
+ | zustand | Yes | Yes | Framework-agnostic |
32
+ | zod | Yes | Yes | Framework-agnostic |
33
+ | @tanstack/react-query | Yes | Yes | First-party TanStack |
34
+ | shadcn/ui | Yes | Yes | Official support both |
35
+ | next-themes | Yes | **No** | Use `tanstack-theme-kit` instead |
36
+ | better-auth | Yes | Yes | Framework-agnostic |
37
+ | resend | Yes | Yes | Server-side only |
38
+ | stripe / polar | Yes | Yes | Server-side SDKs |
39
+ | ai (Vercel AI SDK) | Yes | Yes | Core works; Next.js streaming helpers don't apply |
40
+ | nuqs | Yes | **No** | TanStack Router has built-in `useSearch` |
41
+ | react-hook-form | Yes | Yes | Framework-agnostic |
42
+ | eslint-config-next | Yes | **No** | Next.js-specific; omitted for TanStack |
43
+ | biome | Yes | Yes | Framework-agnostic |
44
+
45
+ ### TanStack CLI reference
46
+ ```bash
47
+ npx @tanstack/cli create my-app -y --tailwind # Non-interactive with Tailwind
48
+ npx @tanstack/cli create --list-add-ons # List available add-ons
49
+ npx @tanstack/cli create my-app --add-ons shadcn # With add-ons
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Feature 2: Claude Code Repo (AGENTS.md → CLAUDE.md) — DONE
55
+
56
+ **Prompt**: Confirm after framework selection, defaults to yes.
57
+
58
+ **Files changed:**
59
+ - `plopfile.js` — New `isClaudeCode` confirm prompt, conditional rendering of CLAUDE.md vs AGENTS.md
60
+ - `templates/CLAUDE.md.hbs` — New concise template (~50 lines) following Anthropic conventions: tech stack, commands, directory structure, code conventions, per-library guidance. Omits verbose "AI Coding Rules" section that AGENTS.md has.
61
+
62
+ ### CLAUDE.md conventions
63
+ - Under 200 lines, shorter is better
64
+ - Structure: WHAT (tech/stack) → WHY (purpose) → HOW (commands/workflow)
65
+ - Put detailed docs in separate files (progressive disclosure)
66
+
67
+ ---
68
+
69
+ ## Feature 3: Database Provider (Supabase vs Convex) — DONE
70
+
71
+ **Prompt**: List choice replacing the old individual Supabase/Drizzle confirms.
72
+
73
+ **Files changed:**
74
+ - `lib/packages.js` — Refactored `Database & ORM` from confirm-based (`items`) to list-based (`type: 'list'`) with `providerConfig` for Supabase+Drizzle, Convex Cloud, Convex Self-hosted. Added `envVars` to all three.
75
+ - `plopfile.js` — Added `isSupabase`/`isConvex` booleans to templateData
76
+ - `templates/AGENTS.md.hbs` — Conditional `db/` vs `convex/` directory structure, conditional security warnings
77
+ - `templates/CLAUDE.md.hbs` — Same conditional updates
78
+
79
+ ### Key technical notes
80
+ - Convex Cloud and Self-hosted use the same `convex` npm package
81
+ - Self-hosted requires Docker + `CONVEX_SELF_HOSTED_URL` and `CONVEX_SELF_HOSTED_ADMIN_KEY` env vars
82
+ - Convex replaces Drizzle entirely (own schema system in `convex/schema.ts`)
83
+ - Better Auth has an official Convex adapter (`@convex-dev/better-auth`)
84
+
85
+ ---
86
+
87
+ ## Feature 4: .env.example Generation — DONE
88
+
89
+ **No prompt** — auto-generated when any selected package has env vars.
90
+
91
+ **Files changed:**
92
+ - `lib/packages.js` — Added `envVars` arrays to: Supabase (4 vars), Convex Cloud (1), Convex Self-hosted (2), Better Auth (2), Resend (1), Stripe (3), Polar (2), AI SDK (1), OpenRouter (1)
93
+ - `templates/env.example.hbs` — New template rendering vars grouped by package with comments
94
+ - `plopfile.js` — Conditional `.env.example` generation, success message includes env note
95
+
96
+ ---
97
+
98
+ ## Other fixes applied
99
+ - Fixed old "quick-vibe" footer in `templates/README.md.hbs` → "vibe-now"
100
+ - Renamed generator from `next-app` to `vibe-app`
101
+
102
+ ---
103
+
104
+ ## Project Structure (current)
105
+ ```
46
106
  vibe-cli/
47
107
  ├── lib/
48
- │ └── packages.js # Centralized configuration
49
- ├── templates/ # HBS templates (README, AGENTS)
50
- ├── index.js # CLI Entry point
51
- ├── plopfile.js # Plop logic & Action orchestrator
108
+ │ └── packages.js # Package configuration with frameworks, envVars
109
+ ├── templates/
110
+ ├── README.md.hbs # Framework-aware README template
111
+ ├── AGENTS.md.hbs # Full AI agent guidance (Cursor, Copilot, etc.)
112
+ │ ├── CLAUDE.md.hbs # Concise Claude Code memory file
113
+ │ └── env.example.hbs # .env.example grouped by package
114
+ ├── index.js # CLI entry point (Plop bootstrap)
115
+ ├── plopfile.js # Prompts, actions, scaffold orchestration
52
116
  ├── package.json
117
+ ├── PLAN.md
53
118
  └── README.md
54
119
  ```
120
+
121
+ ---
122
+
123
+ ## Testing TODO
124
+
125
+ - [ ] Run `npx @tanstack/cli create test-app -y --tailwind` to verify non-interactive mode works
126
+ - [ ] Run `npx convex dev` first-run to check if interactive input is needed
127
+ - [ ] Run full wizard with Next.js + Supabase + Claude Code selection
128
+ - [ ] Run full wizard with TanStack Start + Convex Cloud + AGENTS.md selection
129
+ - [ ] Verify `.env.example` generates correctly with multiple packages
130
+ - [ ] Verify framework-filtered prompts (next-themes hidden for TanStack, nuqs hidden for TanStack, tanstack-theme-kit hidden for Next.js)
131
+ - [ ] Decide: should TanStack Start projects use `--add-ons` flags instead of manual npm install?
132
+
133
+ ---
134
+
135
+ ## Future Enhancements (Post v2)
136
+
137
+ ### High-value
138
+ - **Testing**: Vitest + React Testing Library, Playwright/Cypress for E2E
139
+ - **Analytics**: PostHog, Vercel Analytics, or Plausible
140
+ - **File/Image uploads**: UploadThing or Cloudinary
141
+ - **Caching/Rate limiting**: Upstash Redis
142
+
143
+ ### Nice-to-haves
144
+ - **Monitoring/Error tracking**: Sentry
145
+ - **Background jobs**: Inngest or Trigger.dev
146
+ - **Type-safe API layer**: tRPC (natural fit with TanStack Start)
147
+ - **Icons**: `lucide-react` (already referenced in AGENTS.md guidance but not installed)
148
+
149
+ ### Infra/DX
150
+ - **Docker**: Generate Dockerfile + docker-compose.yml
151
+ - **CI/CD**: GitHub Actions workflow template (lint, test, build)
152
+ - **Deployment target**: Vercel, Netlify, Railway, Fly.io config files
153
+
154
+ ### Framework-aware env vars
155
+ - Use `VITE_` prefix instead of `NEXT_PUBLIC_` for TanStack Start client-exposed vars (Supabase URL, Stripe publishable key)
package/README.md CHANGED
@@ -1,35 +1,38 @@
1
1
  # Vibe Now 🌌
2
2
 
3
- A premium, interactive CLI wizard for scaffolding modern Next.js applications with a perfectly curated stack. Stop running manual `npm install` commands and start building within seconds.
3
+ A premium, interactive CLI wizard for scaffolding modern full-stack React applications with a perfectly curated stack. Choose between **Next.js** or **TanStack Start**, pick your database, payments, and tooling — and start building within seconds.
4
4
 
5
5
  <img width="3680" height="4144" alt="Terminal" src="https://github.com/user-attachments/assets/4a6e97c4-778b-432f-8fe0-46f1193aa543" />
6
6
 
7
7
  ## ✨ Features
8
8
 
9
+ - **Framework Choice**: Scaffold with **Next.js** (App Router) or **TanStack Start** (file-based routing, type-safe).
9
10
  - **Interactive Wizard**: A beautiful CLI experience powered by Plop.js and Inquirer.
10
11
  - **World-Class Feedback**: Engaged progress tracking with **Ora** spinners for a premium feel.
11
- - **Smart Scaffolding**: Automatically orchestrates `create-next-app` and library-specific initializations (like `shadcn init` and `biome init`).
12
- - **Dynamic Documentation**: Automatically generates a project-specific `README.md` and a comprehensive `AGENTS.md` to guide AI assistants (Cursor, Claude) on your stack and standards.
12
+ - **Smart Scaffolding**: Automatically orchestrates `create-next-app` or `@tanstack/cli create` and library-specific initializations (like `shadcn init` and `biome init`).
13
+ - **AI Project Support**: Generates **CLAUDE.md** (for Claude Code) or **AGENTS.md** (for Cursor/other AI assistants) tailored to your chosen stack.
14
+ - **Auto `.env.example`**: Generates a `.env.example` with all required environment variables grouped by package.
13
15
  - **Template System**: Powered by Handlebars templates in the `templates/` directory for highly customized project initialization.
14
16
  - **Safety First**: Project name validation and directory check to prevent accidental overwrites.
15
17
 
16
18
  ## 🛠️ The Curated Stack
17
19
 
18
- ### Base
19
- - **Framework**: Next.js (App Router, TypeScript, Tailwind CSS v4)
20
+ ### Framework (choose one)
21
+ - **Next.js** (App Router, TypeScript, Tailwind CSS v4)
22
+ - **TanStack Start** (File-based routing, TypeScript, Tailwind CSS v4)
20
23
 
21
24
  ### Optional Libraries
22
25
  - **State**: Zustand
23
26
  - **Validation**: Zod
24
27
  - **Data Fetching**: TanStack React Query
25
- - **UI & Components**: shadcn/ui (Initializes & adds all components automatically)
26
- - **Database & ORM**: Supabase JS client, Drizzle ORM (Postgres + Drizzle Kit)
28
+ - **UI & Components**: shadcn/ui, next-themes (Next.js) / tanstack-theme-kit (TanStack)
29
+ - **Database (choose one)**: Supabase + Drizzle ORM, Convex (Cloud), or Convex (Self-hosted)
27
30
  - **Authentication**: Better Auth
28
31
  - **Email**: Resend
29
- - **Payments**: Mutually exclusive selection between **Stripe** and **Polar.sh**
32
+ - **Payments (choose one)**: Stripe or Polar.sh
30
33
  - **AI SDK**: Vercel AI SDK & OpenRouter Provider support
31
- - **Linting & Formatting**: Choose between **ESLint + Prettier** or **Biome** (High speed)
32
- - **UI Helpers**: nuqs, React Hook Form, Day.js, Lodash
34
+ - **Linting & Formatting (choose one)**: ESLint + Prettier or Biome
35
+ - **UI Helpers**: nuqs (Next.js only), React Hook Form, Day.js, Lodash
33
36
 
34
37
  ---
35
38
 
@@ -81,7 +84,7 @@ The CLI uses a group-based configuration for easy maintenance. To add a new libr
81
84
  ```
82
85
 
83
86
  ### 4. Adding New Templates
84
- The wizard currently generates `README.md` and `AGENTS.md` automatically. To add a new template:
87
+ The wizard generates `README.md`, `CLAUDE.md` or `AGENTS.md`, and `.env.example` automatically. To add a new template:
85
88
  1. Create a Handlebars file in the `templates/` directory (e.g., `templates/CONFIG.md.hbs`).
86
89
  2. Open `plopfile.js`.
87
90
  3. Locate the `// 3. Generate README and AGENTS files` section.
package/lib/packages.js CHANGED
@@ -58,8 +58,17 @@ export const PACKAGE_GROUPS = [
58
58
  name: 'next-themes (Dark Mode)',
59
59
  install: ['next-themes'],
60
60
  default: true,
61
+ frameworks: ['nextjs'],
61
62
  guidance: 'Wrap the root layout with `ThemeProvider`. Use the `useTheme` hook to switch between dark, light, and system themes. Ensure `attribute="class"` is set for Tailwind support.',
62
63
  },
64
+ {
65
+ id: 'tanstackThemeKit',
66
+ name: 'tanstack-theme-kit (Dark Mode)',
67
+ install: ['tanstack-theme-kit'],
68
+ default: true,
69
+ frameworks: ['tanstack'],
70
+ guidance: 'A next-themes fork adapted for TanStack Start. Wrap the root layout with `ThemeProvider`. Use `useTheme` hook for dark/light/system themes. Supports SSR without flicker.',
71
+ },
63
72
  ],
64
73
  },
65
74
  {
@@ -71,28 +80,55 @@ export const PACKAGE_GROUPS = [
71
80
  install: ['better-auth'],
72
81
  default: false,
73
82
  guidance: 'Follow the Next.js App Router patterns. Keep the auth logic secure in Server Actions and use the provided middleware for route protection.',
83
+ envVars: [
84
+ { key: 'BETTER_AUTH_SECRET', comment: 'Secret key for Better Auth sessions' },
85
+ { key: 'BETTER_AUTH_URL', comment: 'Base URL of your app (e.g., http://localhost:3000)' },
86
+ ],
74
87
  },
75
88
  ],
76
89
  },
77
90
  {
78
- category: 'Database & ORM',
79
- items: [
80
- {
81
- id: 'supabase',
82
- name: 'Supabase JS client',
83
- install: ['@supabase/supabase-js'],
84
- default: true,
85
- guidance: 'Use the official Supabase SSR package for Next.js. Keep database queries inside Server Components or Server Actions to avoid exposing the Service Role key.',
86
- },
87
- {
88
- id: 'drizzle',
89
- name: 'Drizzle ORM',
90
- install: ['drizzle-orm', 'pg'],
91
+ category: 'Database (Select One)',
92
+ type: 'list',
93
+ id: 'database',
94
+ choices: [
95
+ { name: 'None', value: 'none' },
96
+ { name: 'Supabase (Postgres + Drizzle ORM)', value: 'supabase' },
97
+ { name: 'Convex (Cloud)', value: 'convex_cloud' },
98
+ { name: 'Convex (Self-hosted)', value: 'convex_self' },
99
+ ],
100
+ default: 'supabase',
101
+ providerConfig: {
102
+ supabase: {
103
+ name: 'Supabase + Drizzle',
104
+ install: ['@supabase/supabase-js', 'drizzle-orm', 'pg'],
91
105
  devInstall: ['drizzle-kit'],
92
- default: true,
93
- guidance: 'Define schemas in `db/schema.ts`. Use `drizzle-kit` for migrations. Prefer the `db.query.xyz.findMany()` syntax for better type-safety and relations handling.',
106
+ guidance: 'Use Supabase SSR package for Next.js. Keep DB queries in Server Components/Actions. Define schemas in `db/schema.ts`. Use `drizzle-kit` for migrations. Prefer `db.query.xyz.findMany()` for type-safe relations.',
107
+ envVars: [
108
+ { key: 'NEXT_PUBLIC_SUPABASE_URL', comment: 'Supabase project URL' },
109
+ { key: 'NEXT_PUBLIC_SUPABASE_ANON_KEY', comment: 'Supabase anon/public key' },
110
+ { key: 'SUPABASE_SERVICE_ROLE_KEY', comment: 'Supabase service role key (server-only, never expose to client)' },
111
+ { key: 'DATABASE_URL', comment: 'Postgres connection string for Drizzle' },
112
+ ],
94
113
  },
95
- ],
114
+ convex_cloud: {
115
+ name: 'Convex (Cloud)',
116
+ install: ['convex'],
117
+ guidance: 'Define schema in `convex/schema.ts`. Write queries and mutations as Convex functions in `convex/`. Run `npx convex dev` to sync with cloud. Convex provides real-time reactive queries out of the box.',
118
+ envVars: [
119
+ { key: 'CONVEX_URL', comment: 'Convex deployment URL (auto-set by npx convex dev)' },
120
+ ],
121
+ },
122
+ convex_self: {
123
+ name: 'Convex (Self-hosted)',
124
+ install: ['convex'],
125
+ guidance: 'Self-hosted Convex. Requires Docker (`docker compose up`). Set `CONVEX_SELF_HOSTED_URL` and `CONVEX_SELF_HOSTED_ADMIN_KEY` in `.env.local`. Define schema in `convex/schema.ts`. Run `npx convex dev` to sync.',
126
+ envVars: [
127
+ { key: 'CONVEX_SELF_HOSTED_URL', comment: 'Self-hosted Convex backend URL (e.g., http://127.0.0.1:3210)' },
128
+ { key: 'CONVEX_SELF_HOSTED_ADMIN_KEY', comment: 'Admin key from generate_admin_key.sh' },
129
+ ],
130
+ },
131
+ },
96
132
  },
97
133
  {
98
134
  category: 'Email',
@@ -103,6 +139,9 @@ export const PACKAGE_GROUPS = [
103
139
  install: ['resend'],
104
140
  default: false,
105
141
  guidance: 'Use React Email templates for rich emails. Centralize email logic in a `lib/email.ts` utility.',
142
+ envVars: [
143
+ { key: 'RESEND_API_KEY', comment: 'Resend API key' },
144
+ ],
106
145
  },
107
146
  ],
108
147
  },
@@ -121,11 +160,20 @@ export const PACKAGE_GROUPS = [
121
160
  name: 'Stripe',
122
161
  install: ['stripe', '@stripe/stripe-js', '@stripe/react-stripe-js'],
123
162
  guidance: 'Use Stripe webhooks to handle subscription lifecycle. Centralize price IDs in constants.',
163
+ envVars: [
164
+ { key: 'STRIPE_SECRET_KEY', comment: 'Stripe secret key (server-only)' },
165
+ { key: 'NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY', comment: 'Stripe publishable key' },
166
+ { key: 'STRIPE_WEBHOOK_SECRET', comment: 'Stripe webhook signing secret' },
167
+ ],
124
168
  },
125
169
  polar: {
126
170
  name: 'Polar.sh',
127
171
  install: ['@polar-sh/sdk', '@polar-sh/nextjs'],
128
172
  guidance: 'Integrate using Polar webhooks. Use the Next.js SDK for seamless integration with App Router.',
173
+ envVars: [
174
+ { key: 'POLAR_ACCESS_TOKEN', comment: 'Polar.sh access token' },
175
+ { key: 'POLAR_WEBHOOK_SECRET', comment: 'Polar.sh webhook signing secret' },
176
+ ],
129
177
  },
130
178
  },
131
179
  },
@@ -138,6 +186,9 @@ export const PACKAGE_GROUPS = [
138
186
  install: ['ai'],
139
187
  default: false,
140
188
  guidance: 'Use `streamText` for real-time chat interfaces. Keep LLM configurations in `lib/ai/` and use the UI hooks like `useChat`.',
189
+ envVars: [
190
+ { key: 'OPENAI_API_KEY', comment: 'OpenAI API key (or your preferred AI provider key)' },
191
+ ],
141
192
  },
142
193
  {
143
194
  id: 'openRouter',
@@ -145,6 +196,9 @@ export const PACKAGE_GROUPS = [
145
196
  install: ['@openrouter/ai-sdk-provider'],
146
197
  default: false,
147
198
  guidance: 'Use OpenRouter as a gateway for multiple models. Configure keys in `.env.local` and use the AI SDK provider pattern.',
199
+ envVars: [
200
+ { key: 'OPENROUTER_API_KEY', comment: 'OpenRouter API key' },
201
+ ],
148
202
  },
149
203
  ],
150
204
  },
@@ -163,12 +217,12 @@ export const PACKAGE_GROUPS = [
163
217
  name: 'ESLint + Prettier',
164
218
  devInstall: [
165
219
  'eslint',
166
- 'eslint-config-next',
167
220
  'prettier',
168
221
  'eslint-config-prettier',
169
222
  'eslint-plugin-prettier'
170
223
  ],
171
- guidance: 'Standard Next.js linting combined with Prettier. Ensure `eslint-config-prettier` is the last item in the extends array.',
224
+ devInstallNextjs: ['eslint-config-next'],
225
+ guidance: 'Standard linting combined with Prettier. Ensure `eslint-config-prettier` is the last item in the extends array.',
172
226
  },
173
227
  biome: {
174
228
  name: 'Biome',
@@ -188,6 +242,7 @@ export const PACKAGE_GROUPS = [
188
242
  name: 'nuqs',
189
243
  install: ['nuqs'],
190
244
  default: false,
245
+ frameworks: ['nextjs'],
191
246
  guidance: 'Manage URL search parameters as state. Use the `parseAs...` helpers for strict type-casting.',
192
247
  },
193
248
  {
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "vibe-now",
3
- "version": "1.0.7",
3
+ "version": "1.1.0",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "bin": {
7
- "vibe-now": "./index.js"
7
+ "vibe-now": "index.js"
8
8
  },
9
9
  "scripts": {
10
10
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -16,7 +16,7 @@
16
16
  "keywords": [],
17
17
  "author": "Ed Moss <ed@mossified.com>",
18
18
  "license": "ISC",
19
- "description": "Quickly scaffold a Next.js app with a curated stack",
19
+ "description": "Quickly scaffold a full-stack React app (Next.js or TanStack Start) with a curated stack",
20
20
  "dependencies": {
21
21
  "execa": "^8.0.1",
22
22
  "fs-extra": "^11.3.3",
@@ -25,4 +25,4 @@
25
25
  "ora": "^9.0.0",
26
26
  "plop": "^4.0.4"
27
27
  }
28
- }
28
+ }
package/plopfile.js CHANGED
@@ -8,8 +8,8 @@ import { PACKAGE_GROUPS, ALL_ITEMS } from './lib/packages.js';
8
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
9
 
10
10
  export default function (plop) {
11
- plop.setGenerator('next-app', {
12
- description: 'Scaffold a custom Next.js app',
11
+ plop.setGenerator('vibe-app', {
12
+ description: 'Scaffold a custom full-stack React app',
13
13
  prompts: [
14
14
  {
15
15
  type: 'input',
@@ -27,6 +27,22 @@ export default function (plop) {
27
27
  return true;
28
28
  }
29
29
  },
30
+ {
31
+ type: 'list',
32
+ name: 'framework',
33
+ message: 'Choose your framework:',
34
+ choices: [
35
+ { name: 'Next.js (App Router, stable)', value: 'nextjs' },
36
+ { name: 'TanStack Start (Full-stack, type-safe)', value: 'tanstack' },
37
+ ],
38
+ default: 'nextjs',
39
+ },
40
+ {
41
+ type: 'confirm',
42
+ name: 'isClaudeCode',
43
+ message: 'Is this a Claude Code project? (generates CLAUDE.md instead of AGENTS.md)',
44
+ default: true,
45
+ },
30
46
  ...PACKAGE_GROUPS.flatMap((group) => {
31
47
  if (group.type === 'list') {
32
48
  return [{
@@ -37,45 +53,64 @@ export default function (plop) {
37
53
  default: group.default,
38
54
  }];
39
55
  }
40
- return group.items.map(item => ({
41
- type: 'confirm',
42
- name: item.id,
43
- message: `${group.category}: Include ${item.name}?`,
44
- default: item.default,
45
- }));
56
+ return group.items.map(item => {
57
+ const prompt = {
58
+ type: 'confirm',
59
+ name: item.id,
60
+ message: `${group.category}: Include ${item.name}?`,
61
+ default: item.default,
62
+ };
63
+ if (item.frameworks) {
64
+ prompt.when = (answers) => item.frameworks.includes(answers.framework);
65
+ }
66
+ return prompt;
67
+ });
46
68
  }),
47
69
  ],
48
70
  actions: (data) => {
49
71
  const actions = [];
50
72
  const projectPath = path.join(process.cwd(), data.projectName);
51
73
 
52
- // 1. Create Base Next.js App
74
+ // 1. Create Base App (Next.js or TanStack Start)
53
75
  actions.push({
54
76
  type: 'customSync',
55
77
  async action(answers) {
78
+ const isTanStack = answers.framework === 'tanstack';
79
+ const label = isTanStack ? 'TanStack Start' : 'Next.js';
56
80
  const spinner = ora({
57
- text: `Creating base Next.js app in ${answers.projectName}...`,
81
+ text: `Creating base ${label} app in ${answers.projectName}...`,
58
82
  color: 'cyan',
59
83
  }).start();
60
84
 
61
85
  try {
62
- await execa('npx', [
63
- 'create-next-app@latest',
64
- answers.projectName,
65
- '--ts',
66
- '--tailwind',
67
- '--app',
68
- '--eslint',
69
- '--import-alias',
70
- '@/*',
71
- '--yes',
72
- ], {
73
- env: { ...process.env, npm_config_legacy_peer_deps: 'true' }
74
- });
75
- spinner.succeed('Base Next.js app created!');
76
- return 'Base Next.js app created';
86
+ if (isTanStack) {
87
+ await execa('npx', [
88
+ '@tanstack/cli', 'create',
89
+ answers.projectName,
90
+ '-y',
91
+ '--tailwind',
92
+ ], {
93
+ env: { ...process.env, npm_config_legacy_peer_deps: 'true' }
94
+ });
95
+ } else {
96
+ await execa('npx', [
97
+ 'create-next-app@latest',
98
+ answers.projectName,
99
+ '--ts',
100
+ '--tailwind',
101
+ '--app',
102
+ '--eslint',
103
+ '--import-alias',
104
+ '@/*',
105
+ '--yes',
106
+ ], {
107
+ env: { ...process.env, npm_config_legacy_peer_deps: 'true' }
108
+ });
109
+ }
110
+ spinner.succeed(`Base ${label} app created!`);
111
+ return `Base ${label} app created`;
77
112
  } catch (error) {
78
- spinner.fail('Failed to create Next.js app');
113
+ spinner.fail(`Failed to create ${label} app`);
79
114
  throw error;
80
115
  }
81
116
  },
@@ -88,14 +123,18 @@ export default function (plop) {
88
123
  // Collect standard selections
89
124
  const selectedPackages = ALL_ITEMS.filter(item => answers[item.id]);
90
125
 
91
- // Collect list-based selections (e.g. Payments)
126
+ // Collect list-based selections (e.g. Payments, Database, Linting)
127
+ const isNextjs = answers.framework === 'nextjs';
92
128
  PACKAGE_GROUPS.filter(g => g.type === 'list').forEach(group => {
93
129
  const choice = answers[group.id];
94
130
  if (choice && choice !== 'none' && group.providerConfig[choice]) {
95
- selectedPackages.push({
96
- ...group.providerConfig[choice],
97
- id: `${group.id}_${choice}` // unique internal id
98
- });
131
+ const config = { ...group.providerConfig[choice] };
132
+ // Merge framework-specific dev dependencies
133
+ if (isNextjs && config.devInstallNextjs) {
134
+ config.devInstall = [...(config.devInstall || []), ...config.devInstallNextjs];
135
+ }
136
+ config.id = `${group.id}_${choice}`;
137
+ selectedPackages.push(config);
99
138
  }
100
139
  });
101
140
 
@@ -165,21 +204,42 @@ export default function (plop) {
165
204
  }).start();
166
205
 
167
206
  try {
207
+ const databaseChoice = answers.database || 'none';
208
+ const isTanStack = answers.framework === 'tanstack';
168
209
  const templateData = {
169
210
  projectName: answers.projectName === '.' ? path.basename(projectPath) : answers.projectName,
170
- selectedPackages
211
+ selectedPackages,
212
+ isSupabase: databaseChoice === 'supabase',
213
+ isConvex: databaseChoice === 'convex_cloud' || databaseChoice === 'convex_self',
214
+ isNextjs: !isTanStack,
215
+ isTanStack,
171
216
  };
172
217
 
173
218
  const readmeTmpl = fs.readFileSync(path.join(__dirname, 'templates/README.md.hbs'), 'utf8');
174
- const agentsTmpl = fs.readFileSync(path.join(__dirname, 'templates/AGENTS.md.hbs'), 'utf8');
175
-
176
219
  const renderedReadme = plop.renderString(readmeTmpl, templateData);
177
- const renderedAgents = plop.renderString(agentsTmpl, templateData);
178
-
179
220
  fs.writeFileSync(path.join(projectPath, 'README.md'), renderedReadme);
180
- fs.writeFileSync(path.join(projectPath, 'AGENTS.md'), renderedAgents);
181
221
 
182
- docSpinner.succeed('Documentation and AGENTS.md generated!');
222
+ if (answers.isClaudeCode) {
223
+ const claudeTmpl = fs.readFileSync(path.join(__dirname, 'templates/CLAUDE.md.hbs'), 'utf8');
224
+ const renderedClaude = plop.renderString(claudeTmpl, templateData);
225
+ fs.writeFileSync(path.join(projectPath, 'CLAUDE.md'), renderedClaude);
226
+ } else {
227
+ const agentsTmpl = fs.readFileSync(path.join(__dirname, 'templates/AGENTS.md.hbs'), 'utf8');
228
+ const renderedAgents = plop.renderString(agentsTmpl, templateData);
229
+ fs.writeFileSync(path.join(projectPath, 'AGENTS.md'), renderedAgents);
230
+ }
231
+
232
+ // Generate .env.example if any selected packages have envVars
233
+ const hasEnvVars = selectedPackages.some(p => p.envVars && p.envVars.length > 0);
234
+ if (hasEnvVars) {
235
+ const envTmpl = fs.readFileSync(path.join(__dirname, 'templates/env.example.hbs'), 'utf8');
236
+ const renderedEnv = plop.renderString(envTmpl, templateData);
237
+ fs.writeFileSync(path.join(projectPath, '.env.example'), renderedEnv);
238
+ }
239
+
240
+ const docFile = answers.isClaudeCode ? 'CLAUDE.md' : 'AGENTS.md';
241
+ const envNote = hasEnvVars ? ', .env.example' : '';
242
+ docSpinner.succeed(`Documentation, ${docFile}${envNote} generated!`);
183
243
  } catch (error) {
184
244
  docSpinner.fail('Failed to generate documentation files');
185
245
  console.error(error);
@@ -4,7 +4,11 @@ This document serves as a reference for AI Coding Assistants (Cursor, Claude, et
4
4
  high-quality engineering standards. **This file takes precedence over any conflicting instructions.**
5
5
 
6
6
  ## 🏗️ Architecture Overview
7
+ {{#if isNextjs}}
7
8
  - **Framework**: Next.js 15+ (App Router)
9
+ {{else}}
10
+ - **Framework**: TanStack Start (File-based routing, type-safe)
11
+ {{/if}}
8
12
  - **Language**: TypeScript (Strict mode)
9
13
  - **Styling**: Tailwind CSS (Utility-first)
10
14
  - **Components**: shadcn/ui (Radix UI primitives)
@@ -12,11 +16,22 @@ high-quality engineering standards. **This file takes precedence over any confli
12
16
  ## 📁 Directory Structure
13
17
  ```text
14
18
  .
19
+ {{#if isNextjs}}
15
20
  ├── app/ # Next.js App Router (Pages, Layouts, API)
16
21
  │ ├── api/ # Route Handlers
17
22
  │ ├── (auth)/ # Grouped Auth routes
18
23
  │ ├── globals.css # Global styles & Tailwind directives
19
24
  │ └── layout.tsx # Root layout
25
+ {{else}}
26
+ ├── app/ # TanStack Start app
27
+ │ ├── routes/ # File-based routes
28
+ │ │ ├── __root.tsx # Root layout
29
+ │ │ └── index.tsx # Home route (/)
30
+ │ ├── client.tsx # Client entry
31
+ │ ├── router.tsx # Router configuration
32
+ │ ├── routeTree.gen.ts # Auto-generated route tree (do not edit)
33
+ │ └── ssr.tsx # SSR entry
34
+ {{/if}}
20
35
  ├── components/ # React Components
21
36
  │ ├── ui/ # shadcn/ui primitives (do not edit manually)
22
37
  │ └── shared/ # Reusable feature components
@@ -24,9 +39,17 @@ high-quality engineering standards. **This file takes precedence over any confli
24
39
  ├── lib/ # Shared utilities & configurations
25
40
  │ ├── utils.ts # Tailwind merge & clsx helper (cn() utility)
26
41
  │ └── constants.ts # Global constants & env-validated config
42
+ {{#if isSupabase}}
27
43
  ├── db/ # Database schema & migrations
28
- │ ├── schema.ts # Drizzle/Prisma schema definitions
44
+ │ ├── schema.ts # Drizzle schema definitions
29
45
  │ └── migrations/ # SQL migration files
46
+ {{/if}}
47
+ {{#if isConvex}}
48
+ ├── convex/ # Convex backend
49
+ │ ├── schema.ts # Convex schema definitions
50
+ │ ├── _generated/ # Auto-generated types (do not edit)
51
+ │ └── *.ts # Query & mutation functions
52
+ {{/if}}
30
53
  ├── public/ # Static assets (images, fonts, etc.)
31
54
  ├── scripts/ # Helper scripts (DB seeds, etc.)
32
55
  ├── types/ # Global TypeScript interfaces
@@ -50,7 +73,7 @@ high-quality engineering standards. **This file takes precedence over any confli
50
73
 
51
74
  ## 🛠️ Tech Stack Consistency & Guidance
52
75
  {{#each selectedPackages}}
53
- - **{{this.name}}**: {{#if this.guidance}}{{this.guidance}}{{else}}Use standard patterns as defined in official
76
+ - **{{{this.name}}}**: {{#if this.guidance}}{{{this.guidance}}}{{else}}Use standard patterns as defined in official
54
77
  documentation.{{/if}}
55
78
  {{/each}}
56
79
 
@@ -60,9 +83,15 @@ documentation.{{/if}}
60
83
  - **Conciseness**: Write clean, modular code. Maintain DRY principles.
61
84
  - **Type Safety**: **Avoid `any`.** Use Zod for runtime validation and TypeScript for compile-time safety.
62
85
  - **Modern Syntax**: Prefer functional components, hooks, and async/await.
86
+ {{#if isNextjs}}
63
87
  - **Server-First**: Favor Server Components by default; use `'use client'` sparingly and only at the leaf nodes (forms,
64
88
  triggers).
65
89
  - **Mutations**: Favor Next.js **Server Actions** for all data mutations.
90
+ {{else}}
91
+ - **Server-First**: Use TanStack Start server functions for data loading and mutations.
92
+ - **Routing**: Use file-based routing in `app/routes/`. Dynamic segments use `$` prefix (e.g., `posts.$postId.tsx`).
93
+ - **Data Loading**: Use route loaders for data fetching. Avoid `useState`/`useEffect` for data fetching.
94
+ {{/if}}
66
95
 
67
96
  ### 2. Component Standards
68
97
  - **shadcn/ui**: Never modify components in `components/ui/` unless explicitly requested.
@@ -74,8 +103,13 @@ every component.
74
103
  avoid bundle bloat.
75
104
 
76
105
  ### 3. State & Data
106
+ {{#if isNextjs}}
77
107
  - **Server State**: Use TanStack React Query or Server Actions.
78
108
  - **Client State**: Use Zustand for global state only if necessary. Favor URL state (`nuqs`) for UI-driven state.
109
+ {{else}}
110
+ - **Server State**: Use TanStack React Query or route loaders.
111
+ - **Client State**: Use Zustand for global state only if necessary. Use TanStack Router's built-in `useSearch` for URL state.
112
+ {{/if}}
79
113
  - **Forms**: Always use `useForm` from React Hook Form with a Zod resolver.
80
114
  - **Data Fetching**: Do not use `useState`/`useEffect` for data fetching.
81
115
 
@@ -85,15 +119,19 @@ avoid bundle bloat.
85
119
  - **Empty States**: Always use the `empty.tsx` shadcn component for missing data states.
86
120
 
87
121
  ### 5. Performance & SEO
88
- - **Images**: Use Next.js `
89
- <Image />` for optimized assets.
122
+ {{#if isNextjs}}
123
+ - **Images**: Use Next.js `<Image />` for optimized assets.
124
+ - **SEO**: Implement metadata exports for every page.
125
+ {{else}}
126
+ - **Images**: Optimize images before serving. Use standard `<img>` with proper `loading="lazy"` attributes.
127
+ - **SEO**: Use TanStack Start's `Meta` and `Head` components for per-route metadata.
128
+ {{/if}}
90
129
  - **Accessibility**: Ensure proper semantic HTML and ARIA labels.
91
- - **SEO**: Implement metadata for every page.
92
130
 
93
131
  ## � Things to Avoid
94
132
  - **No Inline Styles**: Use Tailwind classes or `cn()`.
95
133
  - **No Direct Fetching in Hooks**: Avoid `useEffect` for manual fetching.
96
- - **No Security Leaks**: **NEVER** expose the Supabase `service_role` key or secret environment variables to the client.
134
+ - **No Security Leaks**: **NEVER** expose secret environment variables to the client.{{#if isSupabase}} This includes the Supabase `service_role` key.{{/if}}
97
135
  - **No Magic Strings**: Move repeated strings, routes, or query keys to `lib/constants.ts`.
98
136
 
99
137
  ## 📝 Best Practices
@@ -0,0 +1,79 @@
1
+ # {{projectName}}
2
+
3
+ ## Tech Stack
4
+ {{#if isNextjs}}
5
+ - **Framework**: Next.js 15+ (App Router)
6
+ {{else}}
7
+ - **Framework**: TanStack Start (File-based routing, type-safe)
8
+ {{/if}}
9
+ - **Language**: TypeScript (Strict mode)
10
+ - **Styling**: Tailwind CSS (Utility-first)
11
+ {{#each selectedPackages}}
12
+ - **{{{this.name}}}**
13
+ {{/each}}
14
+
15
+ ## Commands
16
+ - Dev: `npm run dev`
17
+ - Build: `npm run build`
18
+ - Lint: `npm run lint`
19
+ - Start: `npm start`
20
+
21
+ ## Directory Structure
22
+ ```
23
+ {{#if isNextjs}}
24
+ app/ # Pages, layouts, API route handlers
25
+ {{else}}
26
+ app/ # TanStack Start app
27
+ routes/ # File-based routes ($param for dynamic segments)
28
+ __root.tsx # Root layout
29
+ index.tsx # Home route (/)
30
+ client.tsx # Client entry
31
+ router.tsx # Router configuration
32
+ routeTree.gen.ts # Auto-generated route tree (do not edit)
33
+ ssr.tsx # SSR entry
34
+ {{/if}}
35
+ components/
36
+ ui/ # shadcn/ui primitives (do not modify)
37
+ shared/ # Reusable feature components
38
+ hooks/ # Custom React hooks (prefixed with use)
39
+ lib/ # Utilities, constants, configs
40
+ utils.ts # cn() helper (Tailwind merge + clsx)
41
+ constants.ts # Global constants & env config
42
+ {{#if isSupabase}}
43
+ db/ # Database schema & migrations (Drizzle)
44
+ schema.ts # Drizzle schema definitions
45
+ migrations/ # SQL migration files
46
+ {{/if}}
47
+ {{#if isConvex}}
48
+ convex/ # Convex backend
49
+ schema.ts # Convex schema definitions
50
+ _generated/ # Auto-generated types (do not edit)
51
+ *.ts # Query & mutation functions
52
+ {{/if}}
53
+ types/ # Global TypeScript interfaces
54
+ public/ # Static assets
55
+ ```
56
+
57
+ ## Code Conventions
58
+ - Files & directories: `kebab-case`
59
+ - Components: named exports, `PascalCase`
60
+ - Variables & functions: `camelCase`
61
+ - Constants: `SCREAMING_SNAKE_CASE`
62
+ - Use `cn()` for all class name merging — never string concatenation
63
+ {{#if isNextjs}}
64
+ - Favor Server Components by default; use `'use client'` sparingly at leaf nodes
65
+ - Use Server Actions for mutations
66
+ {{else}}
67
+ - Use TanStack Start server functions for data loading and mutations
68
+ - Use file-based routing; dynamic segments use `$` prefix (e.g., `posts.$postId.tsx`)
69
+ - Use TanStack Router's `useSearch` for type-safe URL state
70
+ {{/if}}
71
+ - Never use `useState`/`useEffect` for data fetching
72
+ - Never expose secret env vars to the client{{#if isSupabase}} (e.g., Supabase `service_role` key){{/if}}{{#if isConvex}} (e.g., `CONVEX_SELF_HOSTED_ADMIN_KEY`){{/if}}
73
+
74
+ ## Library Guidance
75
+ {{#each selectedPackages}}
76
+ {{#if this.guidance}}
77
+ - **{{{this.name}}}**: {{{this.guidance}}}
78
+ {{/if}}
79
+ {{/each}}
@@ -5,13 +5,17 @@ This project was bootstrapped with [vibe-now](https://github.com/mosster/vibe-no
5
5
  ## 🚀 Installed Stack
6
6
 
7
7
  ### Core
8
+ {{#if isNextjs}}
8
9
  - **Next.js** (App Router)
10
+ {{else}}
11
+ - **TanStack Start** (File-based routing)
12
+ {{/if}}
9
13
  - **TypeScript**
10
14
  - **Tailwind CSS** (v4)
11
15
 
12
16
  ### Selected Features
13
17
  {{#each selectedPackages}}
14
- - **{{this.name}}**
18
+ - **{{{this.name}}}**
15
19
  {{/each}}
16
20
 
17
21
  ## 🛠️ Getting Started
@@ -25,4 +29,4 @@ npm run dev
25
29
  Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
26
30
 
27
31
  ---
28
- Generated by quick-vibe
32
+ Generated by vibe-now
@@ -0,0 +1,13 @@
1
+ # Generated by vibe-now
2
+ # Copy this file to .env.local and fill in your values
3
+
4
+ {{#each selectedPackages}}
5
+ {{#if this.envVars}}
6
+ # --- {{{this.name}}} ---
7
+ {{#each this.envVars}}
8
+ # {{this.comment}}
9
+ {{this.key}}=
10
+ {{/each}}
11
+
12
+ {{/if}}
13
+ {{/each}}
package/tests/TESTS.md ADDED
@@ -0,0 +1,92 @@
1
+ # Test Configurations
2
+
3
+ Generated by `node tests/generate-test-outputs.js` — renders all templates with different configs to verify conditional logic.
4
+
5
+ ## Test 1: `test-nextjs-supabase-claude/`
6
+
7
+ | Setting | Value |
8
+ |---------|-------|
9
+ | Framework | Next.js |
10
+ | AI Doc | CLAUDE.md |
11
+ | Database | Supabase + Drizzle |
12
+ | Payments | Stripe |
13
+ | Linter | ESLint + Prettier |
14
+ | Auth | Better Auth |
15
+ | Theme | next-themes |
16
+
17
+ **Packages:** Zustand, Zod, TanStack React Query, shadcn/ui, next-themes, Better Auth, React Hook Form, Supabase + Drizzle, Stripe, ESLint + Prettier
18
+
19
+ **Generated files:** README.md, CLAUDE.md, .env.example (9 env vars)
20
+
21
+ **Validates:**
22
+ - Next.js-specific directory structure (`app/` with App Router)
23
+ - `db/` directory shown (Supabase/Drizzle)
24
+ - Server Components + Server Actions conventions
25
+ - Supabase `service_role` key security warning
26
+ - Stripe + Supabase + Better Auth env vars in `.env.example`
27
+
28
+ ---
29
+
30
+ ## Test 2: `test-nextjs-convex-agents/`
31
+
32
+ | Setting | Value |
33
+ |---------|-------|
34
+ | Framework | Next.js |
35
+ | AI Doc | AGENTS.md |
36
+ | Database | Convex (Cloud) |
37
+ | Payments | Polar.sh |
38
+ | Linter | Biome |
39
+ | Email | Resend |
40
+ | Theme | next-themes |
41
+
42
+ **Packages:** Zustand, Zod, TanStack React Query, shadcn/ui, next-themes, Resend, React Hook Form, Convex (Cloud), Polar.sh, Biome
43
+
44
+ **Generated files:** README.md, AGENTS.md, .env.example (4 env vars)
45
+
46
+ **Validates:**
47
+ - AGENTS.md generated (not CLAUDE.md) — full AI coding rules doc
48
+ - `convex/` directory shown (not `db/`)
49
+ - Next.js framework with Convex backend
50
+ - Biome guidance instead of ESLint
51
+ - Resend, Convex, Polar env vars in `.env.example`
52
+
53
+ ---
54
+
55
+ ## Test 3: `test-tanstack-convex-self/`
56
+
57
+ | Setting | Value |
58
+ |---------|-------|
59
+ | Framework | TanStack Start |
60
+ | AI Doc | CLAUDE.md |
61
+ | Database | Convex (Self-hosted) |
62
+ | Payments | None |
63
+ | Linter | ESLint + Prettier |
64
+ | Auth | Better Auth |
65
+ | AI | Vercel AI SDK + OpenRouter |
66
+ | Theme | tanstack-theme-kit |
67
+
68
+ **Packages:** Zustand, Zod, TanStack React Query, shadcn/ui, tanstack-theme-kit, Better Auth, Vercel AI SDK, OpenRouter AI SDK Provider, React Hook Form, Convex (Self-hosted), ESLint + Prettier
69
+
70
+ **Generated files:** README.md, CLAUDE.md, .env.example (6 env vars)
71
+
72
+ **Validates:**
73
+ - TanStack Start directory structure (`routes/`, `__root.tsx`, `routeTree.gen.ts`)
74
+ - `$` prefix routing convention (not Next.js `[param]`)
75
+ - `convex/` directory with self-hosted config
76
+ - `CONVEX_SELF_HOSTED_ADMIN_KEY` security warning
77
+ - `useSearch` for URL state (not `nuqs`)
78
+ - tanstack-theme-kit instead of next-themes
79
+ - AI SDK + OpenRouter env vars in `.env.example`
80
+ - No payment env vars (none selected)
81
+
82
+ ---
83
+
84
+ ## Config Validation Tests
85
+
86
+ Run `node tests/validate-config.js` — 103 unit tests covering:
87
+ - Package configuration integrity (ids, names, install arrays)
88
+ - Framework-specific package filtering
89
+ - Env var structure validation
90
+ - ESLint framework-specific installs (`eslint-config-next` only for Next.js)
91
+ - Template rendering for Next.js + Supabase and TanStack + Convex combinations
92
+ - Database group structure and provider configs
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Validates the CLI configuration without running actual installs.
3
+ * Tests prompt generation, package filtering, template rendering, and env vars.
4
+ */
5
+
6
+ import { PACKAGE_GROUPS, ALL_ITEMS } from '../lib/packages.js';
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { createRequire } from 'node:module';
11
+ const require = createRequire(import.meta.url);
12
+ const Handlebars = require('handlebars');
13
+
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+ const rootDir = path.join(__dirname, '..');
16
+
17
+ let passed = 0;
18
+ let failed = 0;
19
+
20
+ function assert(condition, message) {
21
+ if (condition) {
22
+ console.log(` ✅ ${message}`);
23
+ passed++;
24
+ } else {
25
+ console.error(` ❌ ${message}`);
26
+ failed++;
27
+ }
28
+ }
29
+
30
+ // ============================================
31
+ // Test 1: Package configuration integrity
32
+ // ============================================
33
+ console.log('\n📦 Test 1: Package configuration integrity\n');
34
+
35
+ assert(PACKAGE_GROUPS.length > 0, 'PACKAGE_GROUPS is not empty');
36
+ assert(ALL_ITEMS.length > 0, 'ALL_ITEMS is not empty');
37
+
38
+ // Every item should have id, name, and install
39
+ for (const item of ALL_ITEMS) {
40
+ assert(item.id && item.name, `Item ${item.id || '???'} has id and name`);
41
+ assert(Array.isArray(item.install), `${item.id} has install array`);
42
+ }
43
+
44
+ // List-based groups should have providerConfig
45
+ const listGroups = PACKAGE_GROUPS.filter(g => g.type === 'list');
46
+ for (const group of listGroups) {
47
+ assert(group.id, `List group "${group.category}" has id`);
48
+ assert(group.providerConfig, `List group "${group.category}" has providerConfig`);
49
+ assert(group.choices.length > 0, `List group "${group.category}" has choices`);
50
+
51
+ // Every non-none choice should have a providerConfig entry
52
+ for (const choice of group.choices) {
53
+ if (choice.value !== 'none') {
54
+ assert(
55
+ group.providerConfig[choice.value],
56
+ `${group.category}: providerConfig has entry for "${choice.value}"`
57
+ );
58
+ }
59
+ }
60
+ }
61
+
62
+ // ============================================
63
+ // Test 2: Framework-specific packages
64
+ // ============================================
65
+ console.log('\n🔀 Test 2: Framework-specific packages\n');
66
+
67
+ const nextjsOnly = ALL_ITEMS.filter(i => i.frameworks && i.frameworks.includes('nextjs') && !i.frameworks.includes('tanstack'));
68
+ const tanstackOnly = ALL_ITEMS.filter(i => i.frameworks && i.frameworks.includes('tanstack') && !i.frameworks.includes('nextjs'));
69
+ const universal = ALL_ITEMS.filter(i => !i.frameworks);
70
+
71
+ assert(nextjsOnly.length > 0, `Found ${nextjsOnly.length} Next.js-only packages: ${nextjsOnly.map(i => i.id).join(', ')}`);
72
+ assert(tanstackOnly.length > 0, `Found ${tanstackOnly.length} TanStack-only packages: ${tanstackOnly.map(i => i.id).join(', ')}`);
73
+ assert(universal.length > 0, `Found ${universal.length} universal packages`);
74
+
75
+ assert(nextjsOnly.some(i => i.id === 'nextThemes'), 'next-themes is Next.js-only');
76
+ assert(tanstackOnly.some(i => i.id === 'tanstackThemeKit'), 'tanstack-theme-kit is TanStack-only');
77
+ assert(nextjsOnly.some(i => i.id === 'nuqs'), 'nuqs is Next.js-only');
78
+
79
+ // ============================================
80
+ // Test 3: Env vars configuration
81
+ // ============================================
82
+ console.log('\n🔐 Test 3: Env vars configuration\n');
83
+
84
+ // Check packages that should have envVars
85
+ const packagesWithEnvVars = [];
86
+ for (const group of PACKAGE_GROUPS) {
87
+ if (group.providerConfig) {
88
+ for (const [key, config] of Object.entries(group.providerConfig)) {
89
+ if (config.envVars) packagesWithEnvVars.push({ ...config, id: key });
90
+ }
91
+ }
92
+ if (group.items) {
93
+ for (const item of group.items) {
94
+ if (item.envVars) packagesWithEnvVars.push(item);
95
+ }
96
+ }
97
+ }
98
+
99
+ assert(packagesWithEnvVars.length > 0, `Found ${packagesWithEnvVars.length} packages with envVars`);
100
+
101
+ // Validate envVar structure
102
+ for (const pkg of packagesWithEnvVars) {
103
+ for (const envVar of pkg.envVars) {
104
+ assert(envVar.key && envVar.comment, `${pkg.id || pkg.name}: envVar "${envVar.key}" has key and comment`);
105
+ }
106
+ }
107
+
108
+ // Specific checks
109
+ const supabaseConfig = PACKAGE_GROUPS.find(g => g.id === 'database')?.providerConfig?.supabase;
110
+ assert(supabaseConfig?.envVars?.length === 4, `Supabase has 4 env vars`);
111
+
112
+ const convexCloudConfig = PACKAGE_GROUPS.find(g => g.id === 'database')?.providerConfig?.convex_cloud;
113
+ assert(convexCloudConfig?.envVars?.length === 1, `Convex Cloud has 1 env var`);
114
+
115
+ const convexSelfConfig = PACKAGE_GROUPS.find(g => g.id === 'database')?.providerConfig?.convex_self;
116
+ assert(convexSelfConfig?.envVars?.length === 2, `Convex Self-hosted has 2 env vars`);
117
+
118
+ // ============================================
119
+ // Test 4: ESLint framework-specific installs
120
+ // ============================================
121
+ console.log('\n🔧 Test 4: ESLint framework-specific installs\n');
122
+
123
+ const linterGroup = PACKAGE_GROUPS.find(g => g.id === 'linter');
124
+ const eslintConfig = linterGroup?.providerConfig?.eslint;
125
+
126
+ assert(eslintConfig, 'ESLint config exists');
127
+ assert(!eslintConfig.devInstall.includes('eslint-config-next'), 'eslint-config-next NOT in base devInstall');
128
+ assert(eslintConfig.devInstallNextjs?.includes('eslint-config-next'), 'eslint-config-next IS in devInstallNextjs');
129
+
130
+ // ============================================
131
+ // Test 5: Template rendering
132
+ // ============================================
133
+ console.log('\n📄 Test 5: Template rendering\n');
134
+
135
+ const templateFiles = ['README.md.hbs', 'AGENTS.md.hbs', 'CLAUDE.md.hbs', 'env.example.hbs'];
136
+ for (const file of templateFiles) {
137
+ const filePath = path.join(rootDir, 'templates', file);
138
+ assert(fs.existsSync(filePath), `Template exists: ${file}`);
139
+ }
140
+
141
+ // Test rendering with mock data
142
+ const mockData = {
143
+ projectName: 'test-project',
144
+ selectedPackages: [
145
+ { name: 'Zustand', guidance: 'Use stores wisely.' },
146
+ { name: 'Supabase + Drizzle', guidance: 'Keep queries server-side.', envVars: [
147
+ { key: 'NEXT_PUBLIC_SUPABASE_URL', comment: 'Supabase project URL' },
148
+ { key: 'SUPABASE_SERVICE_ROLE_KEY', comment: 'Service role key' },
149
+ ]},
150
+ ],
151
+ isNextjs: true,
152
+ isTanStack: false,
153
+ isSupabase: true,
154
+ isConvex: false,
155
+ };
156
+
157
+ for (const file of templateFiles) {
158
+ const tmplPath = path.join(rootDir, 'templates', file);
159
+ const tmpl = fs.readFileSync(tmplPath, 'utf8');
160
+ try {
161
+ const rendered = Handlebars.compile(tmpl)(mockData);
162
+ assert(rendered.length > 0, `${file} renders (Next.js + Supabase): ${rendered.length} chars`);
163
+
164
+ if (file === 'CLAUDE.md.hbs') {
165
+ assert(rendered.includes('Next.js'), 'CLAUDE.md contains Next.js for nextjs framework');
166
+ assert(rendered.includes('db/'), 'CLAUDE.md contains db/ for Supabase');
167
+ assert(!rendered.includes('convex/'), 'CLAUDE.md does NOT contain convex/ for Supabase');
168
+ }
169
+ if (file === 'env.example.hbs') {
170
+ assert(rendered.includes('NEXT_PUBLIC_SUPABASE_URL'), '.env.example contains Supabase URL');
171
+ assert(rendered.includes('SUPABASE_SERVICE_ROLE_KEY'), '.env.example contains service role key');
172
+ }
173
+ } catch (err) {
174
+ assert(false, `${file} rendering failed: ${err.message}`);
175
+ }
176
+ }
177
+
178
+ // Test TanStack + Convex rendering
179
+ const tanstackConvexData = {
180
+ ...mockData,
181
+ isNextjs: false,
182
+ isTanStack: true,
183
+ isSupabase: false,
184
+ isConvex: true,
185
+ selectedPackages: [
186
+ { name: 'Zustand', guidance: 'Use stores wisely.' },
187
+ { name: 'Convex (Cloud)', guidance: 'Define schema in convex/.', envVars: [
188
+ { key: 'CONVEX_URL', comment: 'Convex deployment URL' },
189
+ ]},
190
+ ],
191
+ };
192
+
193
+ const claudeTmpl = fs.readFileSync(path.join(rootDir, 'templates/CLAUDE.md.hbs'), 'utf8');
194
+ const renderedTanstack = Handlebars.compile(claudeTmpl)(tanstackConvexData);
195
+ assert(renderedTanstack.includes('TanStack Start'), 'CLAUDE.md contains TanStack Start for tanstack framework');
196
+ assert(renderedTanstack.includes('convex/'), 'CLAUDE.md contains convex/ for Convex');
197
+ assert(!renderedTanstack.includes('db/'), 'CLAUDE.md does NOT contain db/ for Convex');
198
+ assert(renderedTanstack.includes('routes/'), 'CLAUDE.md contains routes/ for TanStack');
199
+
200
+ const envTmpl = fs.readFileSync(path.join(rootDir, 'templates/env.example.hbs'), 'utf8');
201
+ const renderedEnv = Handlebars.compile(envTmpl)(tanstackConvexData);
202
+ assert(renderedEnv.includes('CONVEX_URL'), '.env.example contains CONVEX_URL for Convex Cloud');
203
+ assert(!renderedEnv.includes('SUPABASE'), '.env.example does NOT contain Supabase vars for Convex');
204
+
205
+ // ============================================
206
+ // Test 6: Database group structure
207
+ // ============================================
208
+ console.log('\n🗄️ Test 6: Database group structure\n');
209
+
210
+ const dbGroup = PACKAGE_GROUPS.find(g => g.id === 'database');
211
+ assert(dbGroup, 'Database group exists');
212
+ assert(dbGroup.type === 'list', 'Database is a list-type group');
213
+ assert(dbGroup.choices.length === 4, 'Database has 4 choices (none, supabase, convex_cloud, convex_self)');
214
+ assert(dbGroup.providerConfig.supabase, 'Supabase provider config exists');
215
+ assert(dbGroup.providerConfig.convex_cloud, 'Convex Cloud provider config exists');
216
+ assert(dbGroup.providerConfig.convex_self, 'Convex Self-hosted provider config exists');
217
+ assert(dbGroup.providerConfig.supabase.devInstall?.includes('drizzle-kit'), 'Supabase includes drizzle-kit as devDep');
218
+ assert(!dbGroup.providerConfig.convex_cloud.devInstall, 'Convex Cloud has no devDeps');
219
+
220
+ // ============================================
221
+ // Summary
222
+ // ============================================
223
+ console.log(`\n${'='.repeat(40)}`);
224
+ console.log(`Results: ${passed} passed, ${failed} failed`);
225
+ console.log(`${'='.repeat(40)}\n`);
226
+
227
+ process.exit(failed > 0 ? 1 : 0);