create-skit 0.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/README.md +36 -0
- package/bin/create-skit.mjs +1064 -0
- package/lib/module-application.mjs +281 -0
- package/lib/module-resolver.mjs +179 -0
- package/modules/README.md +22 -0
- package/modules/ai-dx/files/AGENTS.md +116 -0
- package/modules/ai-dx/files/ARCHITECTURE.md +103 -0
- package/modules/ai-dx/module.json +14 -0
- package/modules/ai-dx-claude/files/CLAUDE.md +8 -0
- package/modules/ai-dx-claude/module.json +13 -0
- package/modules/ai-dx-cursor/files/.cursor/rules/auth.mdc +53 -0
- package/modules/ai-dx-cursor/files/.cursor/rules/database.mdc +48 -0
- package/modules/ai-dx-cursor/files/.cursor/rules/env.mdc +43 -0
- package/modules/ai-dx-cursor/files/.cursor/rules/nextjs.mdc +58 -0
- package/modules/ai-dx-cursor/files/.cursor/rules/project.mdc +33 -0
- package/modules/ai-dx-cursor/files/.cursor/rules/testing.mdc +55 -0
- package/modules/ai-dx-cursor/module.json +18 -0
- package/modules/ai-dx-gemini/files/.gemini/GEMINI.md +5 -0
- package/modules/ai-dx-gemini/module.json +13 -0
- package/modules/auth-core/module.json +8 -0
- package/modules/auth-github/module.json +20 -0
- package/modules/billing-polar/module.json +20 -0
- package/modules/billing-stripe/module.json +23 -0
- package/modules/dashboard-shell/files/src/app/globals.css +756 -0
- package/modules/dashboard-shell/files/src/app/settings/page.tsx +67 -0
- package/modules/dashboard-shell/module.json +11 -0
- package/modules/db-pg/module.json +21 -0
- package/modules/db-postgresjs/module.json +21 -0
- package/modules/deploy-docker/files/.dockerignore +19 -0
- package/modules/deploy-docker/files/Dockerfile +25 -0
- package/modules/deploy-docker/module.json +11 -0
- package/modules/email-resend/module.json +21 -0
- package/modules/quality-baseline/module.json +8 -0
- package/modules/testing-baseline/module.json +8 -0
- package/package.json +40 -0
- package/presets/README.md +12 -0
- package/presets/blank.json +67 -0
- package/presets/dashboard.json +67 -0
- package/templates/base-web/.env.example +17 -0
- package/templates/base-web/.github/workflows/ci.yml +34 -0
- package/templates/base-web/.husky/pre-commit +3 -0
- package/templates/base-web/.husky/pre-push +3 -0
- package/templates/base-web/.prettierignore +3 -0
- package/templates/base-web/README.md +42 -0
- package/templates/base-web/drizzle.config.ts +16 -0
- package/templates/base-web/eslint.config.mjs +127 -0
- package/templates/base-web/manifest.json +5 -0
- package/templates/base-web/next-env.d.ts +4 -0
- package/templates/base-web/next.config.ts +5 -0
- package/templates/base-web/package.json +75 -0
- package/templates/base-web/playwright.config.ts +21 -0
- package/templates/base-web/prettier.config.mjs +9 -0
- package/templates/base-web/proxy.ts +23 -0
- package/templates/base-web/src/app/api/auth/[...all]/route.ts +5 -0
- package/templates/base-web/src/app/api/billing/checkout/route.ts +26 -0
- package/templates/base-web/src/app/api/billing/portal/route.ts +25 -0
- package/templates/base-web/src/app/api/email/test/route.ts +28 -0
- package/templates/base-web/src/app/api/webhooks/polar/route.ts +5 -0
- package/templates/base-web/src/app/api/webhooks/stripe/route.ts +5 -0
- package/templates/base-web/src/app/billing/page.tsx +55 -0
- package/templates/base-web/src/app/dashboard/page.tsx +15 -0
- package/templates/base-web/src/app/email/page.tsx +46 -0
- package/templates/base-web/src/app/error.tsx +27 -0
- package/templates/base-web/src/app/globals.css +534 -0
- package/templates/base-web/src/app/layout.tsx +19 -0
- package/templates/base-web/src/app/llms-full.txt/route.ts +158 -0
- package/templates/base-web/src/app/llms.txt/route.ts +59 -0
- package/templates/base-web/src/app/loading.tsx +24 -0
- package/templates/base-web/src/app/not-found.tsx +16 -0
- package/templates/base-web/src/app/page.tsx +5 -0
- package/templates/base-web/src/app/sign-in/page.tsx +14 -0
- package/templates/base-web/src/app/sign-up/page.tsx +14 -0
- package/templates/base-web/src/components/auth/email-auth-form.test.tsx +40 -0
- package/templates/base-web/src/components/auth/email-auth-form.tsx +128 -0
- package/templates/base-web/src/components/auth/sign-out-button.tsx +29 -0
- package/templates/base-web/src/db/index.ts +16 -0
- package/templates/base-web/src/db/schema/auth.ts +4 -0
- package/templates/base-web/src/db/schema/index.ts +2 -0
- package/templates/base-web/src/db/schema/projects.ts +17 -0
- package/templates/base-web/src/db/seeds/index.ts +32 -0
- package/templates/base-web/src/lib/auth-client.ts +5 -0
- package/templates/base-web/src/lib/auth-session.ts +21 -0
- package/templates/base-web/src/lib/auth.ts +23 -0
- package/templates/base-web/src/lib/billing/index.ts +37 -0
- package/templates/base-web/src/lib/billing/providers/polar.ts +80 -0
- package/templates/base-web/src/lib/billing/providers/stripe.ts +77 -0
- package/templates/base-web/src/lib/billing/types.ts +25 -0
- package/templates/base-web/src/lib/email/index.ts +19 -0
- package/templates/base-web/src/lib/email/templates.test.ts +12 -0
- package/templates/base-web/src/lib/email/templates.ts +40 -0
- package/templates/base-web/src/lib/env.ts +83 -0
- package/templates/base-web/tests/e2e/home.spec.ts +8 -0
- package/templates/base-web/tsconfig.json +34 -0
- package/templates/base-web/vitest.config.ts +19 -0
- package/templates/blank/.env.example +16 -0
- package/templates/blank/.github/workflows/ci.yml +34 -0
- package/templates/blank/.husky/pre-commit +3 -0
- package/templates/blank/.husky/pre-push +3 -0
- package/templates/blank/.prettierignore +3 -0
- package/templates/blank/drizzle.config.ts +16 -0
- package/templates/blank/eslint.config.mjs +127 -0
- package/templates/blank/next-env.d.ts +4 -0
- package/templates/blank/next.config.ts +5 -0
- package/templates/blank/package.json +75 -0
- package/templates/blank/playwright.config.ts +21 -0
- package/templates/blank/prettier.config.mjs +9 -0
- package/templates/blank/proxy.ts +28 -0
- package/templates/blank/src/app/api/auth/[...all]/route.ts +5 -0
- package/templates/blank/src/app/api/billing/checkout/route.ts +26 -0
- package/templates/blank/src/app/api/billing/portal/route.ts +25 -0
- package/templates/blank/src/app/api/email/test/route.ts +28 -0
- package/templates/blank/src/app/api/webhooks/polar/route.ts +5 -0
- package/templates/blank/src/app/api/webhooks/stripe/route.ts +5 -0
- package/templates/blank/src/app/billing/page.tsx +70 -0
- package/templates/blank/src/app/email/page.tsx +46 -0
- package/templates/blank/src/app/globals.css +394 -0
- package/templates/blank/src/app/layout.tsx +19 -0
- package/templates/blank/src/app/page.tsx +23 -0
- package/templates/blank/src/app/sign-in/page.tsx +18 -0
- package/templates/blank/src/app/sign-up/page.tsx +18 -0
- package/templates/blank/src/components/auth/email-auth-form.test.tsx +39 -0
- package/templates/blank/src/components/auth/email-auth-form.tsx +109 -0
- package/templates/blank/src/components/auth/sign-out-button.tsx +29 -0
- package/templates/blank/src/db/index.ts +16 -0
- package/templates/blank/src/db/schema/auth.ts +4 -0
- package/templates/blank/src/db/schema/index.ts +2 -0
- package/templates/blank/src/db/schema/projects.ts +17 -0
- package/templates/blank/src/db/seeds/index.ts +28 -0
- package/templates/blank/src/lib/auth-client.ts +5 -0
- package/templates/blank/src/lib/auth-session.ts +11 -0
- package/templates/blank/src/lib/auth.ts +23 -0
- package/templates/blank/src/lib/billing/index.ts +37 -0
- package/templates/blank/src/lib/billing/providers/polar.ts +80 -0
- package/templates/blank/src/lib/billing/providers/stripe.ts +77 -0
- package/templates/blank/src/lib/billing/types.ts +25 -0
- package/templates/blank/src/lib/email/index.ts +19 -0
- package/templates/blank/src/lib/email/templates.test.ts +15 -0
- package/templates/blank/src/lib/email/templates.ts +40 -0
- package/templates/blank/src/lib/env.ts +80 -0
- package/templates/blank/tsconfig.json +34 -0
- package/templates/blank/vitest.config.ts +19 -0
- package/templates/dashboard/.env.example +16 -0
- package/templates/dashboard/.github/workflows/ci.yml +34 -0
- package/templates/dashboard/.husky/pre-commit +3 -0
- package/templates/dashboard/.husky/pre-push +3 -0
- package/templates/dashboard/.prettierignore +3 -0
- package/templates/dashboard/drizzle.config.ts +16 -0
- package/templates/dashboard/eslint.config.mjs +127 -0
- package/templates/dashboard/next-env.d.ts +4 -0
- package/templates/dashboard/next.config.ts +5 -0
- package/templates/dashboard/package.json +75 -0
- package/templates/dashboard/playwright.config.ts +21 -0
- package/templates/dashboard/prettier.config.mjs +9 -0
- package/templates/dashboard/proxy.ts +36 -0
- package/templates/dashboard/src/app/api/auth/[...all]/route.ts +5 -0
- package/templates/dashboard/src/app/api/billing/checkout/route.ts +26 -0
- package/templates/dashboard/src/app/api/billing/portal/route.ts +25 -0
- package/templates/dashboard/src/app/api/email/test/route.ts +28 -0
- package/templates/dashboard/src/app/api/webhooks/polar/route.ts +5 -0
- package/templates/dashboard/src/app/api/webhooks/stripe/route.ts +5 -0
- package/templates/dashboard/src/app/billing/layout.tsx +22 -0
- package/templates/dashboard/src/app/billing/page.tsx +73 -0
- package/templates/dashboard/src/app/dashboard/layout.tsx +22 -0
- package/templates/dashboard/src/app/dashboard/page.tsx +104 -0
- package/templates/dashboard/src/app/email/layout.tsx +22 -0
- package/templates/dashboard/src/app/email/page.tsx +54 -0
- package/templates/dashboard/src/app/globals.css +1357 -0
- package/templates/dashboard/src/app/layout.tsx +25 -0
- package/templates/dashboard/src/app/page.tsx +154 -0
- package/templates/dashboard/src/app/settings/layout.tsx +22 -0
- package/templates/dashboard/src/app/settings/page.tsx +85 -0
- package/templates/dashboard/src/app/sign-in/page.tsx +47 -0
- package/templates/dashboard/src/app/sign-up/page.tsx +47 -0
- package/templates/dashboard/src/components/auth/email-auth-form.test.tsx +39 -0
- package/templates/dashboard/src/components/auth/email-auth-form.tsx +160 -0
- package/templates/dashboard/src/components/auth/sign-out-button.tsx +29 -0
- package/templates/dashboard/src/components/dashboard/shell.tsx +158 -0
- package/templates/dashboard/src/db/index.ts +16 -0
- package/templates/dashboard/src/db/schema/auth.ts +4 -0
- package/templates/dashboard/src/db/schema/index.ts +2 -0
- package/templates/dashboard/src/db/schema/projects.ts +17 -0
- package/templates/dashboard/src/db/seeds/index.ts +28 -0
- package/templates/dashboard/src/lib/auth-client.ts +5 -0
- package/templates/dashboard/src/lib/auth-session.ts +11 -0
- package/templates/dashboard/src/lib/auth.ts +41 -0
- package/templates/dashboard/src/lib/billing/index.ts +37 -0
- package/templates/dashboard/src/lib/billing/providers/polar.ts +80 -0
- package/templates/dashboard/src/lib/billing/providers/stripe.ts +77 -0
- package/templates/dashboard/src/lib/billing/types.ts +25 -0
- package/templates/dashboard/src/lib/email/index.ts +19 -0
- package/templates/dashboard/src/lib/email/templates.test.ts +15 -0
- package/templates/dashboard/src/lib/email/templates.ts +40 -0
- package/templates/dashboard/src/lib/env.ts +88 -0
- package/templates/dashboard/tsconfig.json +34 -0
- package/templates/dashboard/vitest.config.ts +19 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-dx",
|
|
3
|
+
"kind": "developer-experience",
|
|
4
|
+
"description": "Shared AI agent baseline: AGENTS.md and ARCHITECTURE.md for all tools.",
|
|
5
|
+
"dependsOn": ["quality-baseline"],
|
|
6
|
+
"conflictsWith": [],
|
|
7
|
+
"replaces": [],
|
|
8
|
+
"template": {
|
|
9
|
+
"copyFiles": [
|
|
10
|
+
"AGENTS.md",
|
|
11
|
+
"ARCHITECTURE.md"
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
@AGENTS.md
|
|
2
|
+
|
|
3
|
+
## Claude Code
|
|
4
|
+
|
|
5
|
+
- Use `/compact` when context gets long
|
|
6
|
+
- Workflow: explore → plan → code → verify → commit
|
|
7
|
+
- Run `pnpm typecheck && pnpm lint:fix` after every change set
|
|
8
|
+
- Prefer reading existing code before generating — patterns are consistent
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-dx-claude",
|
|
3
|
+
"kind": "developer-experience",
|
|
4
|
+
"description": "Claude Code configuration: CLAUDE.md with project-specific workflow.",
|
|
5
|
+
"dependsOn": ["ai-dx"],
|
|
6
|
+
"conflictsWith": [],
|
|
7
|
+
"replaces": [],
|
|
8
|
+
"template": {
|
|
9
|
+
"copyFiles": [
|
|
10
|
+
"CLAUDE.md"
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Better Auth patterns for authentication and session management
|
|
3
|
+
globs: src/lib/auth*, src/app/sign-*/**
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Authentication (Better Auth)
|
|
8
|
+
|
|
9
|
+
## Server-side Session
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { getSession } from "@/lib/auth-session";
|
|
13
|
+
|
|
14
|
+
const session = await getSession();
|
|
15
|
+
// session is { user, session } | null
|
|
16
|
+
if (!session) {
|
|
17
|
+
redirect("/sign-in");
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Client-side Auth
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
"use client";
|
|
25
|
+
|
|
26
|
+
import { authClient } from "@/lib/auth-client";
|
|
27
|
+
|
|
28
|
+
// Sign in
|
|
29
|
+
await authClient.signIn.email({ email, password });
|
|
30
|
+
|
|
31
|
+
// Sign out
|
|
32
|
+
await authClient.signOut();
|
|
33
|
+
|
|
34
|
+
// Get session (client hook)
|
|
35
|
+
const { data: session } = authClient.useSession();
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Config
|
|
39
|
+
|
|
40
|
+
- Server config: `src/lib/auth.ts` (server-only, uses Drizzle adapter)
|
|
41
|
+
- Client config: `src/lib/auth-client.ts`
|
|
42
|
+
- Auth schema: `src/db/schema/auth.ts` (auto-generated via `pnpm auth:generate`)
|
|
43
|
+
- API handler: `src/app/api/auth/[...all]/route.ts`
|
|
44
|
+
|
|
45
|
+
## Route Protection
|
|
46
|
+
|
|
47
|
+
`proxy.ts` checks for session cookies and redirects unauthenticated users. Protected routes are listed in the matcher config there.
|
|
48
|
+
|
|
49
|
+
## Adding Social Providers
|
|
50
|
+
|
|
51
|
+
1. Add provider config in `src/lib/auth.ts`
|
|
52
|
+
2. Add env vars in `src/lib/env.ts`
|
|
53
|
+
3. Add UI button in the auth form component
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Drizzle ORM database patterns and migration workflow
|
|
3
|
+
globs: src/db/**
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Database (Drizzle ORM + PostgreSQL)
|
|
8
|
+
|
|
9
|
+
## Connection
|
|
10
|
+
|
|
11
|
+
`src/db/index.ts` exports `db`. It is `server-only` — never import in client components.
|
|
12
|
+
|
|
13
|
+
## Schema
|
|
14
|
+
|
|
15
|
+
Define tables in `src/db/schema/`, re-export from `src/db/schema/index.ts`.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
|
19
|
+
|
|
20
|
+
export const widgets = pgTable("widgets", {
|
|
21
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
22
|
+
name: text("name").notNull(),
|
|
23
|
+
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull()
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Queries
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { db } from "@/db";
|
|
31
|
+
import { widgets } from "@/db/schema";
|
|
32
|
+
import { eq } from "drizzle-orm";
|
|
33
|
+
|
|
34
|
+
const all = await db.select().from(widgets);
|
|
35
|
+
const one = await db.select().from(widgets).where(eq(widgets.id, id));
|
|
36
|
+
await db.insert(widgets).values({ name: "New" });
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Migration Workflow
|
|
40
|
+
|
|
41
|
+
1. Edit or create schema files
|
|
42
|
+
2. `pnpm db:generate` — creates SQL migration
|
|
43
|
+
3. `pnpm db:migrate` — applies to database
|
|
44
|
+
4. `pnpm db:push` — alternative: push without migration files (dev only)
|
|
45
|
+
|
|
46
|
+
## Seeds
|
|
47
|
+
|
|
48
|
+
Add seed logic in `src/db/seeds/index.ts`, run with `pnpm db:seed`.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Environment variable validation with Zod
|
|
3
|
+
globs: src/lib/env.ts, .env*
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Environment Variables
|
|
8
|
+
|
|
9
|
+
All env access goes through `src/lib/env.ts`. Direct `process.env` is banned by ESLint in every other file.
|
|
10
|
+
|
|
11
|
+
## How It Works
|
|
12
|
+
|
|
13
|
+
1. Zod schema validates every variable at runtime
|
|
14
|
+
2. `readRequiredValue()` provides safe build-time defaults so `next build` succeeds without real secrets
|
|
15
|
+
3. Lazy singleton — parsed once, cached
|
|
16
|
+
4. Proxy export — `env.MY_VAR` reads lazily
|
|
17
|
+
|
|
18
|
+
## Adding a New Variable
|
|
19
|
+
|
|
20
|
+
1. Add Zod field to `envSchema`:
|
|
21
|
+
```ts
|
|
22
|
+
MY_VAR: z.string().min(1).optional(),
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
2. Add to `parseEnv()`:
|
|
26
|
+
```ts
|
|
27
|
+
MY_VAR: process.env.MY_VAR,
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
3. Add default to `.env.example`:
|
|
31
|
+
```
|
|
32
|
+
MY_VAR=example_value
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { env } from "@/lib/env";
|
|
39
|
+
|
|
40
|
+
const url = env.DATABASE_URL;
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Never do `process.env.DATABASE_URL` — ESLint will error.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Next.js App Router patterns and conventions
|
|
3
|
+
globs: src/**/*.tsx, src/**/*.ts
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Next.js Patterns
|
|
8
|
+
|
|
9
|
+
Read bundled docs at `node_modules/next/dist/docs/` before writing Next.js code.
|
|
10
|
+
|
|
11
|
+
## Server Components (default)
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { getSession } from "@/lib/auth-session";
|
|
15
|
+
|
|
16
|
+
export default async function Page() {
|
|
17
|
+
const session = await getSession();
|
|
18
|
+
return <main>{/* render with session data */}</main>;
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Client Components (opt-in)
|
|
23
|
+
|
|
24
|
+
Only when hooks, event handlers, or browser APIs are needed:
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
"use client";
|
|
28
|
+
|
|
29
|
+
import { useState } from "react";
|
|
30
|
+
|
|
31
|
+
export function Counter() {
|
|
32
|
+
const [count, setCount] = useState(0);
|
|
33
|
+
return <button onClick={() => setCount(count + 1)}>{count}</button>;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## API Routes
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { NextResponse } from "next/server";
|
|
41
|
+
|
|
42
|
+
export async function POST(request: Request) {
|
|
43
|
+
const body = await request.json();
|
|
44
|
+
return NextResponse.json({ ok: true });
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Route Protection
|
|
49
|
+
|
|
50
|
+
`proxy.ts` handles auth redirects at the edge. To protect a new route, add it to both the condition and the matcher array in that file.
|
|
51
|
+
|
|
52
|
+
## Metadata
|
|
53
|
+
|
|
54
|
+
Use the `metadata` export in `layout.tsx` or `page.tsx`:
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
export const metadata = { title: "Page Title" };
|
|
58
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Project-wide conventions for this Launchframe-generated app
|
|
3
|
+
globs:
|
|
4
|
+
alwaysApply: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Project Conventions
|
|
8
|
+
|
|
9
|
+
## Stack
|
|
10
|
+
|
|
11
|
+
Next.js 16 · React 19 · TypeScript 5.9 (strict) · Drizzle ORM · Better Auth · Vitest · Playwright
|
|
12
|
+
|
|
13
|
+
## Style
|
|
14
|
+
|
|
15
|
+
- Double quotes, semicolons, no trailing commas, 88-char line width (Prettier)
|
|
16
|
+
- `type` imports: `import { type Foo }` — enforced by ESLint
|
|
17
|
+
- Path alias: `@/*` maps to `./src/*`
|
|
18
|
+
- Import order: builtin → external → internal (`@/**`) → relative (enforced, auto-fixable)
|
|
19
|
+
|
|
20
|
+
## Rules
|
|
21
|
+
|
|
22
|
+
- Server components by default. Only add `"use client"` when hooks or browser APIs are required.
|
|
23
|
+
- Keep route files (`page.tsx`) thin — delegate logic to `src/lib/`.
|
|
24
|
+
- All env access goes through `src/lib/env.ts`. Direct `process.env` is banned by ESLint everywhere else.
|
|
25
|
+
- Run `pnpm typecheck && pnpm lint:fix` after changes.
|
|
26
|
+
|
|
27
|
+
## Where to Look
|
|
28
|
+
|
|
29
|
+
- Routes: `src/app/`
|
|
30
|
+
- Business logic: `src/lib/`
|
|
31
|
+
- Database: `src/db/`
|
|
32
|
+
- Tests: `src/**/*.test.{ts,tsx}` (unit), `tests/e2e/` (e2e)
|
|
33
|
+
- Middleware: `proxy.ts`
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Vitest unit tests and Playwright E2E test conventions
|
|
3
|
+
globs: src/**/*.test.*, tests/**
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Testing
|
|
8
|
+
|
|
9
|
+
## Unit Tests (Vitest)
|
|
10
|
+
|
|
11
|
+
Location: `src/**/*.test.{ts,tsx}` — colocated next to the file they test.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { describe, it, expect } from "vitest";
|
|
15
|
+
|
|
16
|
+
describe("myFunction", () => {
|
|
17
|
+
it("returns expected value", () => {
|
|
18
|
+
expect(myFunction("input")).toBe("output");
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
For React components, use `@testing-library/react`:
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { render, screen } from "@testing-library/react";
|
|
27
|
+
|
|
28
|
+
it("renders heading", () => {
|
|
29
|
+
render(<MyComponent />);
|
|
30
|
+
expect(screen.getByRole("heading")).toHaveTextContent("Title");
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Run: `pnpm test` (single run) or `pnpm test:watch` (watch mode).
|
|
35
|
+
|
|
36
|
+
## E2E Tests (Playwright)
|
|
37
|
+
|
|
38
|
+
Location: `tests/e2e/*.spec.ts`
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { test, expect } from "@playwright/test";
|
|
42
|
+
|
|
43
|
+
test("home page loads", async ({ page }) => {
|
|
44
|
+
await page.goto("/");
|
|
45
|
+
await expect(page.getByRole("heading")).toBeVisible();
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Run: `pnpm test:e2e` (headless) or `pnpm test:e2e:headed` (with browser).
|
|
50
|
+
|
|
51
|
+
Playwright auto-starts the dev server via `playwright.config.ts`.
|
|
52
|
+
|
|
53
|
+
## Pre-commit
|
|
54
|
+
|
|
55
|
+
The Husky pre-commit hook runs `pnpm lint-staged && pnpm typecheck`. Tests run on pre-push.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-dx-cursor",
|
|
3
|
+
"kind": "developer-experience",
|
|
4
|
+
"description": "Cursor IDE rules: modular .mdc files auto-activated by file pattern.",
|
|
5
|
+
"dependsOn": ["ai-dx"],
|
|
6
|
+
"conflictsWith": [],
|
|
7
|
+
"replaces": [],
|
|
8
|
+
"template": {
|
|
9
|
+
"copyFiles": [
|
|
10
|
+
".cursor/rules/project.mdc",
|
|
11
|
+
".cursor/rules/nextjs.mdc",
|
|
12
|
+
".cursor/rules/database.mdc",
|
|
13
|
+
".cursor/rules/auth.mdc",
|
|
14
|
+
".cursor/rules/testing.mdc",
|
|
15
|
+
".cursor/rules/env.mdc"
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-dx-gemini",
|
|
3
|
+
"kind": "developer-experience",
|
|
4
|
+
"description": "Gemini Code Assist configuration: .gemini/GEMINI.md pointing to shared AGENTS.md.",
|
|
5
|
+
"dependsOn": ["ai-dx"],
|
|
6
|
+
"conflictsWith": [],
|
|
7
|
+
"replaces": [],
|
|
8
|
+
"template": {
|
|
9
|
+
"copyFiles": [
|
|
10
|
+
".gemini/GEMINI.md"
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "auth-github",
|
|
3
|
+
"kind": "auth",
|
|
4
|
+
"description": "GitHub OAuth provider for Better Auth.",
|
|
5
|
+
"dependsOn": ["auth-core"],
|
|
6
|
+
"conflictsWith": [],
|
|
7
|
+
"replaces": [],
|
|
8
|
+
"template": {
|
|
9
|
+
"tokenReplacements": {
|
|
10
|
+
"__AUTH_PROVIDER_ENV_EXAMPLE__": "GITHUB_CLIENT_ID=github_oauth_client_id\nGITHUB_CLIENT_SECRET=github_oauth_client_secret",
|
|
11
|
+
"__AUTH_PROVIDER_ENV_SCHEMA__": " GITHUB_CLIENT_ID: z.string().min(1).optional(),\n GITHUB_CLIENT_SECRET: z.string().min(1).optional(),",
|
|
12
|
+
"__AUTH_PROVIDER_ENV_VALUES__": " GITHUB_CLIENT_ID: process.env.GITHUB_CLIENT_ID,\n GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET,",
|
|
13
|
+
"__AUTH_SOCIAL_PROVIDERS_BLOCK__": ",\n socialProviders: {\n github: {\n clientId: env.GITHUB_CLIENT_ID ?? \"\",\n clientSecret: env.GITHUB_CLIENT_SECRET ?? \"\"\n }\n }",
|
|
14
|
+
"__AUTH_SOCIAL_BUTTON_HANDLER__": "\n async function handleGithubSignIn() {\n setIsPending(true);\n setError(null);\n\n try {\n const result = await authClient.signIn.social({\n provider: \"github\",\n callbackURL: \"/dashboard\"\n });\n\n if (result.error) {\n setError(result.error.message ?? \"GitHub sign-in failed.\");\n }\n } catch {\n setError(\"GitHub sign-in failed.\");\n } finally {\n setIsPending(false);\n }\n }\n",
|
|
15
|
+
"__AUTH_SOCIAL_BUTTON__": "\n <div className=\"auth-separator\" aria-hidden=\"true\">\n <span>or continue with</span>\n </div>\n\n <button\n type=\"button\"\n className=\"auth-social-button\"\n onClick={() => {\n void handleGithubSignIn();\n }}\n disabled={isPending}\n >\n {isPending ? \"Working...\" : \"Continue with GitHub\"}\n </button>",
|
|
16
|
+
"__AUTH_SOCIAL_TEST_ASSERTION__": "\n expect(screen.getByRole(\"button\", { name: /continue with github/i })).toBeTruthy();",
|
|
17
|
+
"__AUTH_LLMS_EXTRA__": ", GitHub OAuth"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "billing-polar",
|
|
3
|
+
"kind": "billing",
|
|
4
|
+
"description": "Polar billing provider integration.",
|
|
5
|
+
"dependsOn": ["auth-core"],
|
|
6
|
+
"conflictsWith": [],
|
|
7
|
+
"replaces": [],
|
|
8
|
+
"template": {
|
|
9
|
+
"billingProviders": ["polar"],
|
|
10
|
+
"billing": {
|
|
11
|
+
"imports": [
|
|
12
|
+
"import { PolarBillingProvider } from \"./providers/polar\";"
|
|
13
|
+
],
|
|
14
|
+
"factories": [
|
|
15
|
+
" polar: () => new PolarBillingProvider(),"
|
|
16
|
+
],
|
|
17
|
+
"polarWebhookRouteBody": " const provider = getBillingProvider(\"polar\");\n const rawBody = await request.text();\n\n await provider.handleWebhook({\n headers: request.headers,\n rawBody\n });\n\n return Response.json({ received: true });"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "billing-stripe",
|
|
3
|
+
"kind": "billing",
|
|
4
|
+
"description": "Stripe billing provider integration.",
|
|
5
|
+
"dependsOn": ["auth-core"],
|
|
6
|
+
"conflictsWith": [],
|
|
7
|
+
"replaces": [],
|
|
8
|
+
"template": {
|
|
9
|
+
"billingProviders": ["stripe"],
|
|
10
|
+
"packageDependencies": [
|
|
11
|
+
" \"stripe\": \"18.5.0\","
|
|
12
|
+
],
|
|
13
|
+
"billing": {
|
|
14
|
+
"imports": [
|
|
15
|
+
"import { StripeBillingProvider } from \"./providers/stripe\";"
|
|
16
|
+
],
|
|
17
|
+
"factories": [
|
|
18
|
+
" stripe: () => new StripeBillingProvider(),"
|
|
19
|
+
],
|
|
20
|
+
"stripeWebhookRouteBody": " const provider = getBillingProvider(\"stripe\");\n const rawBody = await request.text();\n\n await provider.handleWebhook({\n headers: request.headers,\n rawBody\n });\n\n return Response.json({ received: true });"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|