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.
- package/.claude/settings.local.json +27 -0
- package/PLAN.md +150 -49
- package/README.md +14 -11
- package/lib/packages.js +73 -18
- package/package.json +4 -4
- package/plopfile.js +98 -38
- package/templates/AGENTS.md.hbs +44 -6
- package/templates/CLAUDE.md.hbs +79 -0
- package/templates/README.md.hbs +6 -2
- package/templates/env.example.hbs +13 -0
- package/tests/TESTS.md +92 -0
- package/tests/validate-config.js +227 -0
|
@@ -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
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
###
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
49
|
-
├── templates/
|
|
50
|
-
├──
|
|
51
|
-
├──
|
|
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
|
|
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
|
-
- **
|
|
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
|
-
###
|
|
19
|
-
- **
|
|
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 (
|
|
26
|
-
- **Database
|
|
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
|
|
32
|
+
- **Payments (choose one)**: Stripe or Polar.sh
|
|
30
33
|
- **AI SDK**: Vercel AI SDK & OpenRouter Provider support
|
|
31
|
-
- **Linting & Formatting
|
|
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
|
|
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
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"vibe-now": "
|
|
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
|
|
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('
|
|
12
|
-
description: 'Scaffold a custom
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
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
|
|
81
|
+
text: `Creating base ${label} app in ${answers.projectName}...`,
|
|
58
82
|
color: 'cyan',
|
|
59
83
|
}).start();
|
|
60
84
|
|
|
61
85
|
try {
|
|
62
|
-
|
|
63
|
-
'
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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(
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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);
|
package/templates/AGENTS.md.hbs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
89
|
-
|
|
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
|
|
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}}
|
package/templates/README.md.hbs
CHANGED
|
@@ -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
|
|
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);
|