@primstack/cli 0.0.1
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/dist/generators/crud/templates/drizzle-table.ts.template +12 -0
- package/dist/generators/crud/templates/handlers.ts.template +136 -0
- package/dist/generators/crud/templates/routes.ts.template +21 -0
- package/dist/generators/crud/templates/schema.ts.template +20 -0
- package/dist/index.js +9618 -0
- package/dist/integrations/analytics/providers/amplitude/templates/src/analytics/index.ts.template +79 -0
- package/dist/integrations/analytics/providers/amplitude/templates/src/analytics/types.ts.template +12 -0
- package/dist/integrations/analytics/providers/mixpanel/templates/src/analytics/index.ts.template +62 -0
- package/dist/integrations/analytics/providers/mixpanel/templates/src/analytics/types.ts.template +12 -0
- package/dist/integrations/analytics/providers/posthog/templates/src/analytics/index.ts.template +67 -0
- package/dist/integrations/analytics/providers/posthog/templates/src/analytics/types.ts.template +12 -0
- package/dist/integrations/auth/providers/authjoy/templates/api/src/middleware/auth.ts.template +89 -0
- package/dist/integrations/auth/providers/authjoy/templates/api/src/routes/auth.ts.template +27 -0
- package/dist/integrations/auth/providers/authjoy/templates/web/src/components/AuthProvider.tsx.template +40 -0
- package/dist/integrations/auth/providers/authjoy/templates/web/src/hooks/use-auth.ts.template +71 -0
- package/dist/integrations/auth/providers/authjoy/templates/web/src/lib/auth.ts.template +59 -0
- package/dist/integrations/auth/providers/authjoy/templates/web/src/pages/account.tsx.template +84 -0
- package/dist/integrations/auth/providers/authjoy/templates/web/src/pages/login.tsx.template +73 -0
- package/dist/integrations/cache/providers/memory/templates/src/cache/index.ts.template +43 -0
- package/dist/integrations/cache/providers/memory/templates/src/cache/types.ts.template +3 -0
- package/dist/integrations/cache/providers/redis/templates/src/cache/index.ts.template +37 -0
- package/dist/integrations/cache/providers/redis/templates/src/cache/types.ts.template +3 -0
- package/dist/integrations/cache/providers/valkey/templates/src/cache/index.ts.template +38 -0
- package/dist/integrations/cache/providers/valkey/templates/src/cache/types.ts.template +3 -0
- package/dist/integrations/db/providers/postgres/templates/drizzle.config.ts.template +10 -0
- package/dist/integrations/db/providers/postgres/templates/src/db/index.ts.template +13 -0
- package/dist/integrations/db/providers/postgres/templates/src/db/migrate.ts.template +19 -0
- package/dist/integrations/db/providers/postgres/templates/src/db/schema/index.ts.template +1 -0
- package/dist/integrations/db/providers/postgres/templates/src/db/schema/users.ts.template +12 -0
- package/dist/integrations/db/providers/postgres/templates/src/db/seed.ts.template +28 -0
- package/dist/integrations/db/providers/sqlite/templates/drizzle.config.ts.template +10 -0
- package/dist/integrations/db/providers/sqlite/templates/src/db/index.ts.template +10 -0
- package/dist/integrations/db/providers/sqlite/templates/src/db/schema/index.ts.template +1 -0
- package/dist/integrations/db/providers/sqlite/templates/src/db/schema/users.ts.template +12 -0
- package/dist/integrations/db/providers/sqlite/templates/src/db/seed.ts.template +28 -0
- package/dist/integrations/db/providers/supabase/templates/drizzle.config.ts.template +10 -0
- package/dist/integrations/db/providers/supabase/templates/src/db/index.ts.template +13 -0
- package/dist/integrations/db/providers/supabase/templates/src/db/migrate.ts.template +19 -0
- package/dist/integrations/db/providers/supabase/templates/src/db/schema/index.ts.template +1 -0
- package/dist/integrations/db/providers/supabase/templates/src/db/schema/users.ts.template +12 -0
- package/dist/integrations/db/providers/supabase/templates/src/db/seed.ts.template +28 -0
- package/dist/integrations/db/providers/turso/templates/drizzle.config.ts.template +11 -0
- package/dist/integrations/db/providers/turso/templates/src/db/index.ts.template +14 -0
- package/dist/integrations/db/providers/turso/templates/src/db/schema/index.ts.template +1 -0
- package/dist/integrations/db/providers/turso/templates/src/db/schema/users.ts.template +12 -0
- package/dist/integrations/db/providers/turso/templates/src/db/seed.ts.template +28 -0
- package/dist/integrations/email/providers/nodemailer/templates/src/email/index.ts.template +24 -0
- package/dist/integrations/email/providers/nodemailer/templates/src/email/templates/index.ts.template +1 -0
- package/dist/integrations/email/providers/nodemailer/templates/src/email/templates/welcome.ts.template +7 -0
- package/dist/integrations/email/providers/nodemailer/templates/src/email/types.ts.template +7 -0
- package/dist/integrations/email/providers/resend/templates/src/email/index.ts.template +18 -0
- package/dist/integrations/email/providers/resend/templates/src/email/templates/index.ts.template +1 -0
- package/dist/integrations/email/providers/resend/templates/src/email/templates/welcome.ts.template +7 -0
- package/dist/integrations/email/providers/resend/templates/src/email/types.ts.template +7 -0
- package/dist/integrations/email/providers/sendgrid/templates/src/email/index.ts.template +16 -0
- package/dist/integrations/email/providers/sendgrid/templates/src/email/templates/index.ts.template +1 -0
- package/dist/integrations/email/providers/sendgrid/templates/src/email/templates/welcome.ts.template +7 -0
- package/dist/integrations/email/providers/sendgrid/templates/src/email/types.ts.template +7 -0
- package/dist/integrations/flags/providers/local/templates/api/src/lib/flags.ts.template +97 -0
- package/dist/integrations/flags/providers/local/templates/api/src/routes/flags.ts.template +36 -0
- package/dist/integrations/flags/providers/local/templates/flags.json.template +8 -0
- package/dist/integrations/flags/providers/local/templates/web/src/hooks/use-flag.ts.template +60 -0
- package/dist/integrations/logging/providers/axiom/templates/src/logging/index.ts.template +56 -0
- package/dist/integrations/logging/providers/axiom/templates/src/logging/types.ts.template +5 -0
- package/dist/integrations/logging/providers/pino/templates/src/logging/index.ts.template +21 -0
- package/dist/integrations/logging/providers/pino/templates/src/logging/types.ts.template +5 -0
- package/dist/integrations/logging/providers/winston/templates/src/logging/index.ts.template +30 -0
- package/dist/integrations/logging/providers/winston/templates/src/logging/types.ts.template +5 -0
- package/dist/integrations/monitor/providers/datadog/templates/src/monitor/index.ts.template +78 -0
- package/dist/integrations/monitor/providers/datadog/templates/src/monitor/types.ts.template +12 -0
- package/dist/integrations/monitor/providers/newrelic/templates/src/monitor/index.ts.template +60 -0
- package/dist/integrations/monitor/providers/newrelic/templates/src/monitor/types.ts.template +12 -0
- package/dist/integrations/monitor/providers/sentry/templates/src/monitor/index.ts.template +70 -0
- package/dist/integrations/monitor/providers/sentry/templates/src/monitor/types.ts.template +12 -0
- package/dist/integrations/queue/providers/bullmq/templates/src/queue/index.ts.template +56 -0
- package/dist/integrations/queue/providers/bullmq/templates/src/queue/types.ts.template +10 -0
- package/dist/integrations/queue/providers/memory/templates/src/queue/index.ts.template +73 -0
- package/dist/integrations/queue/providers/memory/templates/src/queue/types.ts.template +10 -0
- package/dist/integrations/queue/providers/pgboss/templates/src/queue/index.ts.template +34 -0
- package/dist/integrations/queue/providers/pgboss/templates/src/queue/types.ts.template +10 -0
- package/dist/integrations/ratelimit/providers/memory/templates/src/ratelimit/index.ts.template +95 -0
- package/dist/integrations/ratelimit/providers/memory/templates/src/ratelimit/types.ts.template +12 -0
- package/dist/integrations/ratelimit/providers/rate-limiter-flexible/templates/src/ratelimit/index.ts.template +80 -0
- package/dist/integrations/ratelimit/providers/rate-limiter-flexible/templates/src/ratelimit/types.ts.template +12 -0
- package/dist/integrations/ratelimit/providers/upstash/templates/src/ratelimit/index.ts.template +67 -0
- package/dist/integrations/ratelimit/providers/upstash/templates/src/ratelimit/types.ts.template +12 -0
- package/dist/integrations/schedule/providers/bullmq/templates/src/schedule/index.ts.template +81 -0
- package/dist/integrations/schedule/providers/bullmq/templates/src/schedule/types.ts.template +10 -0
- package/dist/integrations/schedule/providers/croner/templates/src/schedule/index.ts.template +47 -0
- package/dist/integrations/schedule/providers/croner/templates/src/schedule/types.ts.template +10 -0
- package/dist/integrations/schedule/providers/node-cron/templates/src/schedule/index.ts.template +45 -0
- package/dist/integrations/schedule/providers/node-cron/templates/src/schedule/types.ts.template +10 -0
- package/dist/integrations/search/providers/algolia/templates/src/search/index.ts.template +52 -0
- package/dist/integrations/search/providers/algolia/templates/src/search/types.ts.template +18 -0
- package/dist/integrations/search/providers/meilisearch/templates/src/search/index.ts.template +49 -0
- package/dist/integrations/search/providers/meilisearch/templates/src/search/types.ts.template +18 -0
- package/dist/integrations/search/providers/typesense/templates/src/search/index.ts.template +71 -0
- package/dist/integrations/search/providers/typesense/templates/src/search/types.ts.template +35 -0
- package/dist/integrations/storage/providers/local/templates/src/storage/index.ts.template +69 -0
- package/dist/integrations/storage/providers/local/templates/src/storage/types.ts.template +12 -0
- package/dist/integrations/storage/providers/r2/templates/src/storage/index.ts.template +80 -0
- package/dist/integrations/storage/providers/r2/templates/src/storage/types.ts.template +12 -0
- package/dist/integrations/storage/providers/s3/templates/src/storage/index.ts.template +78 -0
- package/dist/integrations/storage/providers/s3/templates/src/storage/types.ts.template +12 -0
- package/dist/integrations/stripe/templates/api/src/lib/stripe.ts.template +259 -0
- package/dist/integrations/stripe/templates/api/src/routes/stripe-webhooks.ts.template +284 -0
- package/dist/integrations/stripe/templates/api/stripe.config.ts.template +178 -0
- package/dist/integrations/stripe/templates/shared/src/pricing.ts.template +117 -0
- package/dist/integrations/stripe/templates/shared/src/stripe-types.ts.template +133 -0
- package/dist/integrations/stripe/templates/web/src/components/billing-settings.tsx.template +123 -0
- package/dist/integrations/stripe/templates/web/src/components/pricing-cards.tsx.template +115 -0
- package/dist/integrations/stripe/templates/web/src/pages/pricing.tsx.template +95 -0
- package/dist/templates/api/fastify/.env.example.template +7 -0
- package/dist/templates/api/fastify/.gitignore.template +24 -0
- package/dist/templates/api/fastify/package.json.template +23 -0
- package/dist/templates/api/fastify/src/index.ts.template +52 -0
- package/dist/templates/api/fastify/src/lib/env.ts.template +20 -0
- package/dist/templates/api/fastify/src/routes/health.ts.template +12 -0
- package/dist/templates/api/fastify/tsconfig.json.template +18 -0
- package/dist/templates/api/fastify-postgres/.env.example.template +10 -0
- package/dist/templates/api/fastify-postgres/drizzle.config.ts.template +10 -0
- package/dist/templates/api/fastify-postgres/package.json.template +16 -0
- package/dist/templates/api/fastify-postgres/src/db/index.ts.template +9 -0
- package/dist/templates/api/fastify-postgres/src/db/schema/users.ts.template +12 -0
- package/dist/templates/api/fastify-sqlite/.env.example.template +10 -0
- package/dist/templates/api/fastify-sqlite/drizzle.config.ts.template +10 -0
- package/dist/templates/api/fastify-sqlite/package.json.template +16 -0
- package/dist/templates/api/fastify-sqlite/src/db/index.ts.template +6 -0
- package/dist/templates/api/fastify-sqlite/src/db/schema/users.ts.template +12 -0
- package/dist/templates/api/fastify-supabase/.env.example.template +10 -0
- package/dist/templates/api/fastify-supabase/drizzle.config.ts.template +10 -0
- package/dist/templates/api/fastify-supabase/package.json.template +15 -0
- package/dist/templates/api/fastify-supabase/src/db/index.ts.template +9 -0
- package/dist/templates/api/fastify-supabase/src/db/schema/users.ts.template +12 -0
- package/dist/templates/api/fastify-turso/.env.example.template +11 -0
- package/dist/templates/api/fastify-turso/drizzle.config.ts.template +11 -0
- package/dist/templates/api/fastify-turso/package.json.template +15 -0
- package/dist/templates/api/fastify-turso/src/db/index.ts.template +10 -0
- package/dist/templates/api/fastify-turso/src/db/schema/users.ts.template +12 -0
- package/dist/templates/fullstack/api/.env.example.template +10 -0
- package/dist/templates/fullstack/api/.gitignore.template +4 -0
- package/dist/templates/fullstack/api/drizzle.config.ts.template +14 -0
- package/dist/templates/fullstack/api/package.json.template +33 -0
- package/dist/templates/fullstack/api/src/db/index.ts.template +13 -0
- package/dist/templates/fullstack/api/src/db/schema/api-keys.ts.template +19 -0
- package/dist/templates/fullstack/api/src/db/schema/audit-logs.ts.template +23 -0
- package/dist/templates/fullstack/api/src/db/schema/index.ts.template +8 -0
- package/dist/templates/fullstack/api/src/db/schema/invites.ts.template +19 -0
- package/dist/templates/fullstack/api/src/db/schema/memberships.ts.template +16 -0
- package/dist/templates/fullstack/api/src/db/schema/organizations.ts.template +13 -0
- package/dist/templates/fullstack/api/src/db/schema/plans.ts.template +29 -0
- package/dist/templates/fullstack/api/src/db/schema/subscriptions.ts.template +38 -0
- package/dist/templates/fullstack/api/src/db/schema/users.ts.template +14 -0
- package/dist/templates/fullstack/api/src/index.ts.template +54 -0
- package/dist/templates/fullstack/api/src/lib/env.ts.template +22 -0
- package/dist/templates/fullstack/api/src/routes/health.ts.template +14 -0
- package/dist/templates/fullstack/api/tsconfig.json.template +15 -0
- package/dist/templates/fullstack/root/.gitignore.template +26 -0
- package/dist/templates/fullstack/root/package.json.template +15 -0
- package/dist/templates/fullstack/root/pnpm-workspace.yaml.template +3 -0
- package/dist/templates/fullstack/root/turbo.json.template +17 -0
- package/dist/templates/fullstack/shared/package.json.template +36 -0
- package/dist/templates/fullstack/shared/src/index.ts.template +8 -0
- package/dist/templates/fullstack/shared/src/schemas/api-key.ts.template +28 -0
- package/dist/templates/fullstack/shared/src/schemas/audit-log.ts.template +41 -0
- package/dist/templates/fullstack/shared/src/schemas/index.ts.template +8 -0
- package/dist/templates/fullstack/shared/src/schemas/invite.ts.template +25 -0
- package/dist/templates/fullstack/shared/src/schemas/membership.ts.template +20 -0
- package/dist/templates/fullstack/shared/src/schemas/organization.ts.template +18 -0
- package/dist/templates/fullstack/shared/src/schemas/plan.ts.template +38 -0
- package/dist/templates/fullstack/shared/src/schemas/subscription.ts.template +56 -0
- package/dist/templates/fullstack/shared/src/schemas/user.ts.template +21 -0
- package/dist/templates/fullstack/shared/src/types/index.ts.template +75 -0
- package/dist/templates/fullstack/shared/src/validators/index.ts.template +53 -0
- package/dist/templates/fullstack/shared/tsconfig.json.template +17 -0
- package/dist/templates/fullstack/web/.gitignore.template +3 -0
- package/dist/templates/fullstack/web/index.html.template +13 -0
- package/dist/templates/fullstack/web/package.json.template +23 -0
- package/dist/templates/fullstack/web/src/App.tsx.template +47 -0
- package/dist/templates/fullstack/web/src/index.css.template +54 -0
- package/dist/templates/fullstack/web/src/main.tsx.template +10 -0
- package/dist/templates/fullstack/web/src/vite-env.d.ts.template +1 -0
- package/dist/templates/fullstack/web/tsconfig.json.template +21 -0
- package/dist/templates/fullstack/web/tsconfig.node.json.template +11 -0
- package/dist/templates/fullstack/web/vite.config.ts.template +15 -0
- package/dist/templates/hosted/root/.env.local.template +13 -0
- package/dist/templates/hosted/root/.gitignore.template +32 -0
- package/dist/templates/hosted/root/CLAUDE.md.template +139 -0
- package/dist/templates/hosted/root/drizzle.config.ts.template +10 -0
- package/dist/templates/hosted/root/next.config.ts.template +15 -0
- package/dist/templates/hosted/root/package.json.template +40 -0
- package/dist/templates/hosted/root/postcss.config.mjs.template +9 -0
- package/dist/templates/hosted/root/primstack.config.json.template +5 -0
- package/dist/templates/hosted/root/tailwind.config.ts.template +14 -0
- package/dist/templates/hosted/root/tsconfig.json.template +25 -0
- package/dist/templates/hosted/root/wrangler.toml.template +9 -0
- package/dist/templates/hosted/src/app/actions/example.ts.template +50 -0
- package/dist/templates/hosted/src/app/api/health/route.ts.template +5 -0
- package/dist/templates/hosted/src/app/auth/login/page.tsx.template +32 -0
- package/dist/templates/hosted/src/app/globals.css.template +59 -0
- package/dist/templates/hosted/src/app/layout.tsx.template +24 -0
- package/dist/templates/hosted/src/app/page.tsx.template +34 -0
- package/dist/templates/hosted/src/db/migrations/0000_initial.sql.template +43 -0
- package/dist/templates/hosted/src/db/schema.ts.template +52 -0
- package/dist/templates/hosted/src/env.d.ts.template +10 -0
- package/dist/templates/hosted/src/instrumentation.ts.template +6 -0
- package/dist/templates/hosted/src/lib/auth.ts.template +35 -0
- package/dist/templates/hosted/src/lib/db.ts.template +17 -0
- package/dist/templates/hosted/src/middleware.ts.template +6 -0
- package/package.json +46 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A Primstack hosted project — Next.js + Primstack UI + D1 + Auth.js, deployed to Primstack hosting.
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
|
|
7
|
+
- **Framework:** Next.js 14 (App Router, edge runtime)
|
|
8
|
+
- **Components:** @primstack/ui (60+ Radix-based React components)
|
|
9
|
+
- **Styling:** Tailwind CSS + Primstack design tokens
|
|
10
|
+
- **Database:** Cloudflare D1 via Drizzle ORM (local D1 via `setupDevPlatform` in dev)
|
|
11
|
+
- **Auth:** Auth.js v5 with GitHub provider + Drizzle adapter
|
|
12
|
+
- **Hosting:** Primstack hosting (Cloudflare Pages via `@cloudflare/next-on-pages`)
|
|
13
|
+
|
|
14
|
+
## Component Usage
|
|
15
|
+
|
|
16
|
+
Import from `@primstack/ui/<component>`:
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import { Button } from '@primstack/ui/button';
|
|
20
|
+
import { Card, CardHeader, CardContent } from '@primstack/ui/card';
|
|
21
|
+
import { Input } from '@primstack/ui/input';
|
|
22
|
+
import { Badge } from '@primstack/ui/badge';
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
All components accept `className` for Tailwind overrides. Use `variant` and `size` props.
|
|
26
|
+
|
|
27
|
+
## Adding a Database Table
|
|
28
|
+
|
|
29
|
+
1. **Define schema** in `src/db/schema.ts`:
|
|
30
|
+
```ts
|
|
31
|
+
export const posts = sqliteTable('posts', {
|
|
32
|
+
id: text('id').primaryKey(),
|
|
33
|
+
title: text('title').notNull(),
|
|
34
|
+
content: text('content'),
|
|
35
|
+
userId: text('user_id').references(() => authUsers.id),
|
|
36
|
+
createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`),
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
2. **Generate migration:**
|
|
41
|
+
```bash
|
|
42
|
+
npx drizzle-kit generate
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
3. **Migrate locally:**
|
|
46
|
+
```bash
|
|
47
|
+
npx drizzle-kit migrate
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
4. Migrations apply automatically on `prim deploy`.
|
|
51
|
+
|
|
52
|
+
## Adding a Server Action
|
|
53
|
+
|
|
54
|
+
Create `src/app/actions/posts.ts`:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
'use server';
|
|
58
|
+
import { getDatabase } from '@/lib/db';
|
|
59
|
+
import { posts } from '@/db/schema';
|
|
60
|
+
|
|
61
|
+
export async function createPost(formData: FormData) {
|
|
62
|
+
const db = getDatabase();
|
|
63
|
+
const id = crypto.randomUUID();
|
|
64
|
+
await db.insert(posts).values({
|
|
65
|
+
id,
|
|
66
|
+
title: formData.get('title') as string,
|
|
67
|
+
content: formData.get('content') as string,
|
|
68
|
+
});
|
|
69
|
+
return { success: true, id };
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`getDatabase()` uses the D1 binding via `getRequestContext()` in both production and local dev (via `setupDevPlatform` in `src/instrumentation.ts`).
|
|
74
|
+
|
|
75
|
+
## Adding a New Page
|
|
76
|
+
|
|
77
|
+
All pages that use CF bindings (D1, env vars) must declare edge runtime:
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
export const runtime = 'edge';
|
|
81
|
+
|
|
82
|
+
export default function MyPage() {
|
|
83
|
+
return <div>Hello</div>;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Adding Auth-Protected Pages
|
|
88
|
+
|
|
89
|
+
1. Use `auth()` in a Server Component:
|
|
90
|
+
```tsx
|
|
91
|
+
import { auth } from '@/lib/auth';
|
|
92
|
+
import { redirect } from 'next/navigation';
|
|
93
|
+
|
|
94
|
+
export const runtime = 'edge';
|
|
95
|
+
|
|
96
|
+
export default async function ProtectedPage() {
|
|
97
|
+
const session = await auth();
|
|
98
|
+
if (!session) redirect('/auth/login');
|
|
99
|
+
|
|
100
|
+
return <div>Welcome {session.user?.name}</div>;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
2. Or use the middleware (already protects `/dashboard/*`).
|
|
105
|
+
|
|
106
|
+
## Environment Variables
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
prim env set AUTH_SECRET=... # Auth.js secret (required)
|
|
110
|
+
prim env set GITHUB_CLIENT_ID=... # GitHub OAuth app
|
|
111
|
+
prim env set GITHUB_CLIENT_SECRET=... # GitHub OAuth secret
|
|
112
|
+
prim deploy # Vars injected automatically
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
D1 is bound automatically via the Primstack platform — no need to set `DATABASE_URL`.
|
|
116
|
+
|
|
117
|
+
## Deployment
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
prim deploy # Deploy to production
|
|
121
|
+
prim deploy --preview # Preview deployment
|
|
122
|
+
prim deploy --no-build # Upload pre-built output
|
|
123
|
+
prim deploy -m "message" # Deploy with message
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Key Files
|
|
127
|
+
|
|
128
|
+
| File | Purpose |
|
|
129
|
+
|------|---------|
|
|
130
|
+
| `src/app/layout.tsx` | Root layout with ThemeProvider |
|
|
131
|
+
| `src/app/page.tsx` | Home page |
|
|
132
|
+
| `src/db/schema.ts` | Database schema (Drizzle) |
|
|
133
|
+
| `src/lib/db.ts` | Database connection (D1 via getRequestContext) |
|
|
134
|
+
| `src/lib/auth.ts` | Auth.js configuration + Drizzle adapter |
|
|
135
|
+
| `src/middleware.ts` | Auth middleware |
|
|
136
|
+
| `src/instrumentation.ts` | setupDevPlatform for local D1 |
|
|
137
|
+
| `src/app/actions/*.ts` | Server Actions |
|
|
138
|
+
| `wrangler.toml` | CF Pages config (D1 binding) |
|
|
139
|
+
| `primstack.config.json` | Project configuration |
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { NextConfig } from 'next';
|
|
2
|
+
|
|
3
|
+
const nextConfig: NextConfig = {
|
|
4
|
+
transpilePackages: [
|
|
5
|
+
'@primstack/ui',
|
|
6
|
+
'@primstack/theme',
|
|
7
|
+
'@primstack/tokens',
|
|
8
|
+
'@primstack/core',
|
|
9
|
+
],
|
|
10
|
+
images: {
|
|
11
|
+
unoptimized: true,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default nextConfig;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "next dev",
|
|
8
|
+
"build": "next build && npx @cloudflare/next-on-pages",
|
|
9
|
+
"preview": "npx wrangler pages dev .vercel/output/static --compatibility-date=2024-09-23",
|
|
10
|
+
"lint": "next lint",
|
|
11
|
+
"db:generate": "drizzle-kit generate",
|
|
12
|
+
"db:migrate": "drizzle-kit migrate",
|
|
13
|
+
"typecheck": "tsc --noEmit"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@auth/drizzle-adapter": "^1.7.0",
|
|
17
|
+
"@primstack/core": "latest",
|
|
18
|
+
"@primstack/theme": "latest",
|
|
19
|
+
"@primstack/tokens": "latest",
|
|
20
|
+
"@primstack/ui": "latest",
|
|
21
|
+
"drizzle-orm": "^0.38.0",
|
|
22
|
+
"next": "^14.2.0",
|
|
23
|
+
"next-auth": "^5.0.0-beta.25",
|
|
24
|
+
"react": "^18.3.0",
|
|
25
|
+
"react-dom": "^18.3.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@cloudflare/next-on-pages": "^1.13.0",
|
|
29
|
+
"@types/node": "^20.0.0",
|
|
30
|
+
"@types/react": "^18.3.0",
|
|
31
|
+
"@types/react-dom": "^18.3.0",
|
|
32
|
+
"autoprefixer": "^10.4.0",
|
|
33
|
+
"better-sqlite3": "^11.0.0",
|
|
34
|
+
"drizzle-kit": "^0.30.0",
|
|
35
|
+
"postcss": "^8.4.0",
|
|
36
|
+
"tailwindcss": "^3.4.0",
|
|
37
|
+
"typescript": "^5.7.0",
|
|
38
|
+
"wrangler": "^4.0.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "preserve",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [
|
|
17
|
+
{ "name": "next" }
|
|
18
|
+
],
|
|
19
|
+
"paths": {
|
|
20
|
+
"@/*": ["./src/*"]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
24
|
+
"exclude": ["node_modules"]
|
|
25
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { eq } from 'drizzle-orm';
|
|
4
|
+
import { getDatabase } from '@/lib/db';
|
|
5
|
+
import { items } from '@/db/schema';
|
|
6
|
+
|
|
7
|
+
export async function createItem(formData: FormData) {
|
|
8
|
+
const title = formData.get('title') as string;
|
|
9
|
+
const description = formData.get('description') as string | null;
|
|
10
|
+
|
|
11
|
+
if (!title?.trim()) {
|
|
12
|
+
return { error: 'Title is required' };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const db = getDatabase();
|
|
16
|
+
const id = crypto.randomUUID();
|
|
17
|
+
|
|
18
|
+
await db.insert(items).values({
|
|
19
|
+
id,
|
|
20
|
+
title: title.trim(),
|
|
21
|
+
description: description?.trim() || null,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return { success: true, id };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function listItems() {
|
|
28
|
+
const db = getDatabase();
|
|
29
|
+
return db.select().from(items).orderBy(items.createdAt);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function toggleItem(id: string) {
|
|
33
|
+
const db = getDatabase();
|
|
34
|
+
const [item] = await db.select().from(items).where(eq(items.id, id)).limit(1);
|
|
35
|
+
|
|
36
|
+
if (!item) return { error: 'Item not found' };
|
|
37
|
+
|
|
38
|
+
await db
|
|
39
|
+
.update(items)
|
|
40
|
+
.set({ completed: !item.completed })
|
|
41
|
+
.where(eq(items.id, id));
|
|
42
|
+
|
|
43
|
+
return { success: true };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function deleteItem(id: string) {
|
|
47
|
+
const db = getDatabase();
|
|
48
|
+
await db.delete(items).where(eq(items.id, id));
|
|
49
|
+
return { success: true };
|
|
50
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { signIn } from '@/lib/auth';
|
|
2
|
+
import { Button } from '@primstack/ui/button';
|
|
3
|
+
import { Heading } from '@primstack/ui/heading';
|
|
4
|
+
import { Text } from '@primstack/ui/text';
|
|
5
|
+
|
|
6
|
+
export const runtime = 'edge';
|
|
7
|
+
|
|
8
|
+
export default function LoginPage() {
|
|
9
|
+
return (
|
|
10
|
+
<div className="min-h-screen flex items-center justify-center bg-background px-4">
|
|
11
|
+
<div className="w-full max-w-md space-y-6 text-center">
|
|
12
|
+
<div>
|
|
13
|
+
<Heading size="h2">Welcome back</Heading>
|
|
14
|
+
<Text className="text-muted-foreground mt-1">
|
|
15
|
+
Sign in to {{PROJECT_NAME}}
|
|
16
|
+
</Text>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<form
|
|
20
|
+
action={async () => {
|
|
21
|
+
'use server';
|
|
22
|
+
await signIn('github', { redirectTo: '/' });
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<Button type="submit" className="w-full" size="lg">
|
|
26
|
+
Sign in with GitHub
|
|
27
|
+
</Button>
|
|
28
|
+
</form>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
:root {
|
|
7
|
+
--background: 0 0% 100%;
|
|
8
|
+
--foreground: 0 0% 3.9%;
|
|
9
|
+
--card: 0 0% 100%;
|
|
10
|
+
--card-foreground: 0 0% 3.9%;
|
|
11
|
+
--popover: 0 0% 100%;
|
|
12
|
+
--popover-foreground: 0 0% 3.9%;
|
|
13
|
+
--primary: 0 0% 9%;
|
|
14
|
+
--primary-foreground: 0 0% 98%;
|
|
15
|
+
--secondary: 0 0% 96.1%;
|
|
16
|
+
--secondary-foreground: 0 0% 9%;
|
|
17
|
+
--muted: 0 0% 96.1%;
|
|
18
|
+
--muted-foreground: 0 0% 45.1%;
|
|
19
|
+
--accent: 0 0% 96.1%;
|
|
20
|
+
--accent-foreground: 0 0% 9%;
|
|
21
|
+
--destructive: 0 84.2% 60.2%;
|
|
22
|
+
--destructive-foreground: 0 0% 98%;
|
|
23
|
+
--border: 0 0% 89.8%;
|
|
24
|
+
--input: 0 0% 89.8%;
|
|
25
|
+
--ring: 0 0% 3.9%;
|
|
26
|
+
--radius: 0.5rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.dark {
|
|
30
|
+
--background: 0 0% 3.9%;
|
|
31
|
+
--foreground: 0 0% 98%;
|
|
32
|
+
--card: 0 0% 3.9%;
|
|
33
|
+
--card-foreground: 0 0% 98%;
|
|
34
|
+
--popover: 0 0% 3.9%;
|
|
35
|
+
--popover-foreground: 0 0% 98%;
|
|
36
|
+
--primary: 0 0% 98%;
|
|
37
|
+
--primary-foreground: 0 0% 9%;
|
|
38
|
+
--secondary: 0 0% 14.9%;
|
|
39
|
+
--secondary-foreground: 0 0% 98%;
|
|
40
|
+
--muted: 0 0% 14.9%;
|
|
41
|
+
--muted-foreground: 0 0% 63.9%;
|
|
42
|
+
--accent: 0 0% 14.9%;
|
|
43
|
+
--accent-foreground: 0 0% 98%;
|
|
44
|
+
--destructive: 0 62.8% 30.6%;
|
|
45
|
+
--destructive-foreground: 0 0% 98%;
|
|
46
|
+
--border: 0 0% 14.9%;
|
|
47
|
+
--input: 0 0% 14.9%;
|
|
48
|
+
--ring: 0 0% 83.1%;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@layer base {
|
|
53
|
+
* {
|
|
54
|
+
@apply border-border;
|
|
55
|
+
}
|
|
56
|
+
body {
|
|
57
|
+
@apply bg-background text-foreground;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
import { ThemeProvider } from '@primstack/theme';
|
|
3
|
+
import './globals.css';
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: '{{PROJECT_NAME}}',
|
|
7
|
+
description: 'Built with Primstack',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default function RootLayout({
|
|
11
|
+
children,
|
|
12
|
+
}: {
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<html lang="en" suppressHydrationWarning>
|
|
17
|
+
<body className="min-h-screen antialiased">
|
|
18
|
+
<ThemeProvider personality="{{PERSONALITY}}">
|
|
19
|
+
{children}
|
|
20
|
+
</ThemeProvider>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const runtime = 'edge';
|
|
2
|
+
|
|
3
|
+
export default function Home() {
|
|
4
|
+
return (
|
|
5
|
+
<main className="flex min-h-screen flex-col items-center justify-center p-8">
|
|
6
|
+
<div className="max-w-2xl text-center space-y-6">
|
|
7
|
+
<h1 className="text-4xl font-bold tracking-tight">
|
|
8
|
+
{{PROJECT_NAME}}
|
|
9
|
+
</h1>
|
|
10
|
+
<p className="text-lg text-muted-foreground">
|
|
11
|
+
Built with Primstack. Edit{' '}
|
|
12
|
+
<code className="rounded bg-muted px-1.5 py-0.5 text-sm font-mono">
|
|
13
|
+
src/app/page.tsx
|
|
14
|
+
</code>{' '}
|
|
15
|
+
to get started.
|
|
16
|
+
</p>
|
|
17
|
+
<div className="flex gap-4 justify-center">
|
|
18
|
+
<a
|
|
19
|
+
href="https://primstack.ai/docs"
|
|
20
|
+
className="inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow hover:bg-primary/90"
|
|
21
|
+
>
|
|
22
|
+
Documentation
|
|
23
|
+
</a>
|
|
24
|
+
<a
|
|
25
|
+
href="https://primstack.ai/components"
|
|
26
|
+
className="inline-flex items-center justify-center rounded-md border border-input bg-background px-4 py-2 text-sm font-medium shadow-sm hover:bg-accent hover:text-accent-foreground"
|
|
27
|
+
>
|
|
28
|
+
Components
|
|
29
|
+
</a>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</main>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
-- Auth.js tables
|
|
2
|
+
CREATE TABLE IF NOT EXISTS auth_users (
|
|
3
|
+
id TEXT PRIMARY KEY,
|
|
4
|
+
name TEXT,
|
|
5
|
+
email TEXT UNIQUE,
|
|
6
|
+
email_verified INTEGER,
|
|
7
|
+
image TEXT
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
CREATE TABLE IF NOT EXISTS accounts (
|
|
11
|
+
id TEXT PRIMARY KEY,
|
|
12
|
+
user_id TEXT NOT NULL REFERENCES auth_users(id) ON DELETE CASCADE,
|
|
13
|
+
type TEXT NOT NULL,
|
|
14
|
+
provider TEXT NOT NULL,
|
|
15
|
+
provider_account_id TEXT NOT NULL,
|
|
16
|
+
refresh_token TEXT,
|
|
17
|
+
access_token TEXT,
|
|
18
|
+
expires_at INTEGER,
|
|
19
|
+
token_type TEXT,
|
|
20
|
+
scope TEXT,
|
|
21
|
+
id_token TEXT,
|
|
22
|
+
session_state TEXT
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
CREATE UNIQUE INDEX IF NOT EXISTS accounts_provider_idx ON accounts(provider, provider_account_id);
|
|
26
|
+
|
|
27
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
28
|
+
id TEXT PRIMARY KEY,
|
|
29
|
+
session_token TEXT NOT NULL UNIQUE,
|
|
30
|
+
user_id TEXT NOT NULL REFERENCES auth_users(id) ON DELETE CASCADE,
|
|
31
|
+
expires INTEGER NOT NULL
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
-- App tables
|
|
35
|
+
CREATE TABLE IF NOT EXISTS items (
|
|
36
|
+
id TEXT PRIMARY KEY,
|
|
37
|
+
title TEXT NOT NULL,
|
|
38
|
+
description TEXT,
|
|
39
|
+
completed INTEGER NOT NULL DEFAULT 0,
|
|
40
|
+
user_id TEXT REFERENCES auth_users(id) ON DELETE SET NULL,
|
|
41
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
42
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
43
|
+
);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { sqliteTable, text, integer, uniqueIndex } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
import { sql } from 'drizzle-orm';
|
|
3
|
+
|
|
4
|
+
// ── Auth.js tables ─────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export const authUsers = sqliteTable('auth_users', {
|
|
7
|
+
id: text('id').primaryKey(),
|
|
8
|
+
name: text('name'),
|
|
9
|
+
email: text('email').unique(),
|
|
10
|
+
emailVerified: integer('email_verified', { mode: 'timestamp' }),
|
|
11
|
+
image: text('image'),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const accounts = sqliteTable('accounts', {
|
|
15
|
+
id: text('id').primaryKey(),
|
|
16
|
+
userId: text('user_id').notNull().references(() => authUsers.id, { onDelete: 'cascade' }),
|
|
17
|
+
type: text('type').notNull(),
|
|
18
|
+
provider: text('provider').notNull(),
|
|
19
|
+
providerAccountId: text('provider_account_id').notNull(),
|
|
20
|
+
refreshToken: text('refresh_token'),
|
|
21
|
+
accessToken: text('access_token'),
|
|
22
|
+
expiresAt: integer('expires_at'),
|
|
23
|
+
tokenType: text('token_type'),
|
|
24
|
+
scope: text('scope'),
|
|
25
|
+
idToken: text('id_token'),
|
|
26
|
+
sessionState: text('session_state'),
|
|
27
|
+
}, (table) => [
|
|
28
|
+
uniqueIndex('accounts_provider_idx').on(table.provider, table.providerAccountId),
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
export const sessions = sqliteTable('sessions', {
|
|
32
|
+
id: text('id').primaryKey(),
|
|
33
|
+
sessionToken: text('session_token').notNull().unique(),
|
|
34
|
+
userId: text('user_id').notNull().references(() => authUsers.id, { onDelete: 'cascade' }),
|
|
35
|
+
expires: integer('expires', { mode: 'timestamp' }).notNull(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// ── App tables ─────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
export const items = sqliteTable('items', {
|
|
41
|
+
id: text('id').primaryKey(),
|
|
42
|
+
title: text('title').notNull(),
|
|
43
|
+
description: text('description'),
|
|
44
|
+
completed: integer('completed', { mode: 'boolean' }).notNull().default(false),
|
|
45
|
+
userId: text('user_id').references(() => authUsers.id, { onDelete: 'set null' }),
|
|
46
|
+
createdAt: integer('created_at', { mode: 'timestamp' })
|
|
47
|
+
.notNull()
|
|
48
|
+
.default(sql`(unixepoch())`),
|
|
49
|
+
updatedAt: integer('updated_at', { mode: 'timestamp' })
|
|
50
|
+
.notNull()
|
|
51
|
+
.default(sql`(unixepoch())`),
|
|
52
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import NextAuth from 'next-auth';
|
|
2
|
+
import GitHub from 'next-auth/providers/github';
|
|
3
|
+
import { DrizzleAdapter } from '@auth/drizzle-adapter';
|
|
4
|
+
import { getDatabase } from './db';
|
|
5
|
+
import { authUsers, accounts, sessions } from '../db/schema';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Auth.js v5 configuration.
|
|
9
|
+
*
|
|
10
|
+
* Uses the Drizzle adapter to persist users, accounts, and sessions in D1.
|
|
11
|
+
* JWT strategy is used so middleware can validate sessions at the edge
|
|
12
|
+
* without a DB round-trip on every request.
|
|
13
|
+
*
|
|
14
|
+
* NextAuth is called with a callback so the adapter is created per-request
|
|
15
|
+
* (getDatabase() requires an active request context for getRequestContext()).
|
|
16
|
+
*/
|
|
17
|
+
export const { handlers, signIn, signOut, auth } = NextAuth(() => ({
|
|
18
|
+
adapter: DrizzleAdapter(getDatabase(), {
|
|
19
|
+
usersTable: authUsers,
|
|
20
|
+
accountsTable: accounts,
|
|
21
|
+
sessionsTable: sessions,
|
|
22
|
+
}),
|
|
23
|
+
providers: [
|
|
24
|
+
GitHub({
|
|
25
|
+
clientId: process.env.GITHUB_CLIENT_ID!,
|
|
26
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
27
|
+
}),
|
|
28
|
+
],
|
|
29
|
+
session: {
|
|
30
|
+
strategy: 'jwt',
|
|
31
|
+
},
|
|
32
|
+
pages: {
|
|
33
|
+
signIn: '/auth/login',
|
|
34
|
+
},
|
|
35
|
+
}));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getRequestContext } from '@cloudflare/next-on-pages';
|
|
2
|
+
import { drizzle } from 'drizzle-orm/d1';
|
|
3
|
+
import * as schema from '../db/schema';
|
|
4
|
+
|
|
5
|
+
export type AppDb = ReturnType<typeof drizzle<typeof schema>>;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get the database for the current request.
|
|
9
|
+
*
|
|
10
|
+
* In production: uses the D1 binding from Cloudflare Pages.
|
|
11
|
+
* In development: uses a local D1 instance via setupDevPlatform()
|
|
12
|
+
* (configured in src/instrumentation.ts).
|
|
13
|
+
*/
|
|
14
|
+
export function getDatabase() {
|
|
15
|
+
const d1 = getRequestContext().env.DB;
|
|
16
|
+
return drizzle(d1, { schema });
|
|
17
|
+
}
|