@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.
Files changed (210) hide show
  1. package/dist/generators/crud/templates/drizzle-table.ts.template +12 -0
  2. package/dist/generators/crud/templates/handlers.ts.template +136 -0
  3. package/dist/generators/crud/templates/routes.ts.template +21 -0
  4. package/dist/generators/crud/templates/schema.ts.template +20 -0
  5. package/dist/index.js +9618 -0
  6. package/dist/integrations/analytics/providers/amplitude/templates/src/analytics/index.ts.template +79 -0
  7. package/dist/integrations/analytics/providers/amplitude/templates/src/analytics/types.ts.template +12 -0
  8. package/dist/integrations/analytics/providers/mixpanel/templates/src/analytics/index.ts.template +62 -0
  9. package/dist/integrations/analytics/providers/mixpanel/templates/src/analytics/types.ts.template +12 -0
  10. package/dist/integrations/analytics/providers/posthog/templates/src/analytics/index.ts.template +67 -0
  11. package/dist/integrations/analytics/providers/posthog/templates/src/analytics/types.ts.template +12 -0
  12. package/dist/integrations/auth/providers/authjoy/templates/api/src/middleware/auth.ts.template +89 -0
  13. package/dist/integrations/auth/providers/authjoy/templates/api/src/routes/auth.ts.template +27 -0
  14. package/dist/integrations/auth/providers/authjoy/templates/web/src/components/AuthProvider.tsx.template +40 -0
  15. package/dist/integrations/auth/providers/authjoy/templates/web/src/hooks/use-auth.ts.template +71 -0
  16. package/dist/integrations/auth/providers/authjoy/templates/web/src/lib/auth.ts.template +59 -0
  17. package/dist/integrations/auth/providers/authjoy/templates/web/src/pages/account.tsx.template +84 -0
  18. package/dist/integrations/auth/providers/authjoy/templates/web/src/pages/login.tsx.template +73 -0
  19. package/dist/integrations/cache/providers/memory/templates/src/cache/index.ts.template +43 -0
  20. package/dist/integrations/cache/providers/memory/templates/src/cache/types.ts.template +3 -0
  21. package/dist/integrations/cache/providers/redis/templates/src/cache/index.ts.template +37 -0
  22. package/dist/integrations/cache/providers/redis/templates/src/cache/types.ts.template +3 -0
  23. package/dist/integrations/cache/providers/valkey/templates/src/cache/index.ts.template +38 -0
  24. package/dist/integrations/cache/providers/valkey/templates/src/cache/types.ts.template +3 -0
  25. package/dist/integrations/db/providers/postgres/templates/drizzle.config.ts.template +10 -0
  26. package/dist/integrations/db/providers/postgres/templates/src/db/index.ts.template +13 -0
  27. package/dist/integrations/db/providers/postgres/templates/src/db/migrate.ts.template +19 -0
  28. package/dist/integrations/db/providers/postgres/templates/src/db/schema/index.ts.template +1 -0
  29. package/dist/integrations/db/providers/postgres/templates/src/db/schema/users.ts.template +12 -0
  30. package/dist/integrations/db/providers/postgres/templates/src/db/seed.ts.template +28 -0
  31. package/dist/integrations/db/providers/sqlite/templates/drizzle.config.ts.template +10 -0
  32. package/dist/integrations/db/providers/sqlite/templates/src/db/index.ts.template +10 -0
  33. package/dist/integrations/db/providers/sqlite/templates/src/db/schema/index.ts.template +1 -0
  34. package/dist/integrations/db/providers/sqlite/templates/src/db/schema/users.ts.template +12 -0
  35. package/dist/integrations/db/providers/sqlite/templates/src/db/seed.ts.template +28 -0
  36. package/dist/integrations/db/providers/supabase/templates/drizzle.config.ts.template +10 -0
  37. package/dist/integrations/db/providers/supabase/templates/src/db/index.ts.template +13 -0
  38. package/dist/integrations/db/providers/supabase/templates/src/db/migrate.ts.template +19 -0
  39. package/dist/integrations/db/providers/supabase/templates/src/db/schema/index.ts.template +1 -0
  40. package/dist/integrations/db/providers/supabase/templates/src/db/schema/users.ts.template +12 -0
  41. package/dist/integrations/db/providers/supabase/templates/src/db/seed.ts.template +28 -0
  42. package/dist/integrations/db/providers/turso/templates/drizzle.config.ts.template +11 -0
  43. package/dist/integrations/db/providers/turso/templates/src/db/index.ts.template +14 -0
  44. package/dist/integrations/db/providers/turso/templates/src/db/schema/index.ts.template +1 -0
  45. package/dist/integrations/db/providers/turso/templates/src/db/schema/users.ts.template +12 -0
  46. package/dist/integrations/db/providers/turso/templates/src/db/seed.ts.template +28 -0
  47. package/dist/integrations/email/providers/nodemailer/templates/src/email/index.ts.template +24 -0
  48. package/dist/integrations/email/providers/nodemailer/templates/src/email/templates/index.ts.template +1 -0
  49. package/dist/integrations/email/providers/nodemailer/templates/src/email/templates/welcome.ts.template +7 -0
  50. package/dist/integrations/email/providers/nodemailer/templates/src/email/types.ts.template +7 -0
  51. package/dist/integrations/email/providers/resend/templates/src/email/index.ts.template +18 -0
  52. package/dist/integrations/email/providers/resend/templates/src/email/templates/index.ts.template +1 -0
  53. package/dist/integrations/email/providers/resend/templates/src/email/templates/welcome.ts.template +7 -0
  54. package/dist/integrations/email/providers/resend/templates/src/email/types.ts.template +7 -0
  55. package/dist/integrations/email/providers/sendgrid/templates/src/email/index.ts.template +16 -0
  56. package/dist/integrations/email/providers/sendgrid/templates/src/email/templates/index.ts.template +1 -0
  57. package/dist/integrations/email/providers/sendgrid/templates/src/email/templates/welcome.ts.template +7 -0
  58. package/dist/integrations/email/providers/sendgrid/templates/src/email/types.ts.template +7 -0
  59. package/dist/integrations/flags/providers/local/templates/api/src/lib/flags.ts.template +97 -0
  60. package/dist/integrations/flags/providers/local/templates/api/src/routes/flags.ts.template +36 -0
  61. package/dist/integrations/flags/providers/local/templates/flags.json.template +8 -0
  62. package/dist/integrations/flags/providers/local/templates/web/src/hooks/use-flag.ts.template +60 -0
  63. package/dist/integrations/logging/providers/axiom/templates/src/logging/index.ts.template +56 -0
  64. package/dist/integrations/logging/providers/axiom/templates/src/logging/types.ts.template +5 -0
  65. package/dist/integrations/logging/providers/pino/templates/src/logging/index.ts.template +21 -0
  66. package/dist/integrations/logging/providers/pino/templates/src/logging/types.ts.template +5 -0
  67. package/dist/integrations/logging/providers/winston/templates/src/logging/index.ts.template +30 -0
  68. package/dist/integrations/logging/providers/winston/templates/src/logging/types.ts.template +5 -0
  69. package/dist/integrations/monitor/providers/datadog/templates/src/monitor/index.ts.template +78 -0
  70. package/dist/integrations/monitor/providers/datadog/templates/src/monitor/types.ts.template +12 -0
  71. package/dist/integrations/monitor/providers/newrelic/templates/src/monitor/index.ts.template +60 -0
  72. package/dist/integrations/monitor/providers/newrelic/templates/src/monitor/types.ts.template +12 -0
  73. package/dist/integrations/monitor/providers/sentry/templates/src/monitor/index.ts.template +70 -0
  74. package/dist/integrations/monitor/providers/sentry/templates/src/monitor/types.ts.template +12 -0
  75. package/dist/integrations/queue/providers/bullmq/templates/src/queue/index.ts.template +56 -0
  76. package/dist/integrations/queue/providers/bullmq/templates/src/queue/types.ts.template +10 -0
  77. package/dist/integrations/queue/providers/memory/templates/src/queue/index.ts.template +73 -0
  78. package/dist/integrations/queue/providers/memory/templates/src/queue/types.ts.template +10 -0
  79. package/dist/integrations/queue/providers/pgboss/templates/src/queue/index.ts.template +34 -0
  80. package/dist/integrations/queue/providers/pgboss/templates/src/queue/types.ts.template +10 -0
  81. package/dist/integrations/ratelimit/providers/memory/templates/src/ratelimit/index.ts.template +95 -0
  82. package/dist/integrations/ratelimit/providers/memory/templates/src/ratelimit/types.ts.template +12 -0
  83. package/dist/integrations/ratelimit/providers/rate-limiter-flexible/templates/src/ratelimit/index.ts.template +80 -0
  84. package/dist/integrations/ratelimit/providers/rate-limiter-flexible/templates/src/ratelimit/types.ts.template +12 -0
  85. package/dist/integrations/ratelimit/providers/upstash/templates/src/ratelimit/index.ts.template +67 -0
  86. package/dist/integrations/ratelimit/providers/upstash/templates/src/ratelimit/types.ts.template +12 -0
  87. package/dist/integrations/schedule/providers/bullmq/templates/src/schedule/index.ts.template +81 -0
  88. package/dist/integrations/schedule/providers/bullmq/templates/src/schedule/types.ts.template +10 -0
  89. package/dist/integrations/schedule/providers/croner/templates/src/schedule/index.ts.template +47 -0
  90. package/dist/integrations/schedule/providers/croner/templates/src/schedule/types.ts.template +10 -0
  91. package/dist/integrations/schedule/providers/node-cron/templates/src/schedule/index.ts.template +45 -0
  92. package/dist/integrations/schedule/providers/node-cron/templates/src/schedule/types.ts.template +10 -0
  93. package/dist/integrations/search/providers/algolia/templates/src/search/index.ts.template +52 -0
  94. package/dist/integrations/search/providers/algolia/templates/src/search/types.ts.template +18 -0
  95. package/dist/integrations/search/providers/meilisearch/templates/src/search/index.ts.template +49 -0
  96. package/dist/integrations/search/providers/meilisearch/templates/src/search/types.ts.template +18 -0
  97. package/dist/integrations/search/providers/typesense/templates/src/search/index.ts.template +71 -0
  98. package/dist/integrations/search/providers/typesense/templates/src/search/types.ts.template +35 -0
  99. package/dist/integrations/storage/providers/local/templates/src/storage/index.ts.template +69 -0
  100. package/dist/integrations/storage/providers/local/templates/src/storage/types.ts.template +12 -0
  101. package/dist/integrations/storage/providers/r2/templates/src/storage/index.ts.template +80 -0
  102. package/dist/integrations/storage/providers/r2/templates/src/storage/types.ts.template +12 -0
  103. package/dist/integrations/storage/providers/s3/templates/src/storage/index.ts.template +78 -0
  104. package/dist/integrations/storage/providers/s3/templates/src/storage/types.ts.template +12 -0
  105. package/dist/integrations/stripe/templates/api/src/lib/stripe.ts.template +259 -0
  106. package/dist/integrations/stripe/templates/api/src/routes/stripe-webhooks.ts.template +284 -0
  107. package/dist/integrations/stripe/templates/api/stripe.config.ts.template +178 -0
  108. package/dist/integrations/stripe/templates/shared/src/pricing.ts.template +117 -0
  109. package/dist/integrations/stripe/templates/shared/src/stripe-types.ts.template +133 -0
  110. package/dist/integrations/stripe/templates/web/src/components/billing-settings.tsx.template +123 -0
  111. package/dist/integrations/stripe/templates/web/src/components/pricing-cards.tsx.template +115 -0
  112. package/dist/integrations/stripe/templates/web/src/pages/pricing.tsx.template +95 -0
  113. package/dist/templates/api/fastify/.env.example.template +7 -0
  114. package/dist/templates/api/fastify/.gitignore.template +24 -0
  115. package/dist/templates/api/fastify/package.json.template +23 -0
  116. package/dist/templates/api/fastify/src/index.ts.template +52 -0
  117. package/dist/templates/api/fastify/src/lib/env.ts.template +20 -0
  118. package/dist/templates/api/fastify/src/routes/health.ts.template +12 -0
  119. package/dist/templates/api/fastify/tsconfig.json.template +18 -0
  120. package/dist/templates/api/fastify-postgres/.env.example.template +10 -0
  121. package/dist/templates/api/fastify-postgres/drizzle.config.ts.template +10 -0
  122. package/dist/templates/api/fastify-postgres/package.json.template +16 -0
  123. package/dist/templates/api/fastify-postgres/src/db/index.ts.template +9 -0
  124. package/dist/templates/api/fastify-postgres/src/db/schema/users.ts.template +12 -0
  125. package/dist/templates/api/fastify-sqlite/.env.example.template +10 -0
  126. package/dist/templates/api/fastify-sqlite/drizzle.config.ts.template +10 -0
  127. package/dist/templates/api/fastify-sqlite/package.json.template +16 -0
  128. package/dist/templates/api/fastify-sqlite/src/db/index.ts.template +6 -0
  129. package/dist/templates/api/fastify-sqlite/src/db/schema/users.ts.template +12 -0
  130. package/dist/templates/api/fastify-supabase/.env.example.template +10 -0
  131. package/dist/templates/api/fastify-supabase/drizzle.config.ts.template +10 -0
  132. package/dist/templates/api/fastify-supabase/package.json.template +15 -0
  133. package/dist/templates/api/fastify-supabase/src/db/index.ts.template +9 -0
  134. package/dist/templates/api/fastify-supabase/src/db/schema/users.ts.template +12 -0
  135. package/dist/templates/api/fastify-turso/.env.example.template +11 -0
  136. package/dist/templates/api/fastify-turso/drizzle.config.ts.template +11 -0
  137. package/dist/templates/api/fastify-turso/package.json.template +15 -0
  138. package/dist/templates/api/fastify-turso/src/db/index.ts.template +10 -0
  139. package/dist/templates/api/fastify-turso/src/db/schema/users.ts.template +12 -0
  140. package/dist/templates/fullstack/api/.env.example.template +10 -0
  141. package/dist/templates/fullstack/api/.gitignore.template +4 -0
  142. package/dist/templates/fullstack/api/drizzle.config.ts.template +14 -0
  143. package/dist/templates/fullstack/api/package.json.template +33 -0
  144. package/dist/templates/fullstack/api/src/db/index.ts.template +13 -0
  145. package/dist/templates/fullstack/api/src/db/schema/api-keys.ts.template +19 -0
  146. package/dist/templates/fullstack/api/src/db/schema/audit-logs.ts.template +23 -0
  147. package/dist/templates/fullstack/api/src/db/schema/index.ts.template +8 -0
  148. package/dist/templates/fullstack/api/src/db/schema/invites.ts.template +19 -0
  149. package/dist/templates/fullstack/api/src/db/schema/memberships.ts.template +16 -0
  150. package/dist/templates/fullstack/api/src/db/schema/organizations.ts.template +13 -0
  151. package/dist/templates/fullstack/api/src/db/schema/plans.ts.template +29 -0
  152. package/dist/templates/fullstack/api/src/db/schema/subscriptions.ts.template +38 -0
  153. package/dist/templates/fullstack/api/src/db/schema/users.ts.template +14 -0
  154. package/dist/templates/fullstack/api/src/index.ts.template +54 -0
  155. package/dist/templates/fullstack/api/src/lib/env.ts.template +22 -0
  156. package/dist/templates/fullstack/api/src/routes/health.ts.template +14 -0
  157. package/dist/templates/fullstack/api/tsconfig.json.template +15 -0
  158. package/dist/templates/fullstack/root/.gitignore.template +26 -0
  159. package/dist/templates/fullstack/root/package.json.template +15 -0
  160. package/dist/templates/fullstack/root/pnpm-workspace.yaml.template +3 -0
  161. package/dist/templates/fullstack/root/turbo.json.template +17 -0
  162. package/dist/templates/fullstack/shared/package.json.template +36 -0
  163. package/dist/templates/fullstack/shared/src/index.ts.template +8 -0
  164. package/dist/templates/fullstack/shared/src/schemas/api-key.ts.template +28 -0
  165. package/dist/templates/fullstack/shared/src/schemas/audit-log.ts.template +41 -0
  166. package/dist/templates/fullstack/shared/src/schemas/index.ts.template +8 -0
  167. package/dist/templates/fullstack/shared/src/schemas/invite.ts.template +25 -0
  168. package/dist/templates/fullstack/shared/src/schemas/membership.ts.template +20 -0
  169. package/dist/templates/fullstack/shared/src/schemas/organization.ts.template +18 -0
  170. package/dist/templates/fullstack/shared/src/schemas/plan.ts.template +38 -0
  171. package/dist/templates/fullstack/shared/src/schemas/subscription.ts.template +56 -0
  172. package/dist/templates/fullstack/shared/src/schemas/user.ts.template +21 -0
  173. package/dist/templates/fullstack/shared/src/types/index.ts.template +75 -0
  174. package/dist/templates/fullstack/shared/src/validators/index.ts.template +53 -0
  175. package/dist/templates/fullstack/shared/tsconfig.json.template +17 -0
  176. package/dist/templates/fullstack/web/.gitignore.template +3 -0
  177. package/dist/templates/fullstack/web/index.html.template +13 -0
  178. package/dist/templates/fullstack/web/package.json.template +23 -0
  179. package/dist/templates/fullstack/web/src/App.tsx.template +47 -0
  180. package/dist/templates/fullstack/web/src/index.css.template +54 -0
  181. package/dist/templates/fullstack/web/src/main.tsx.template +10 -0
  182. package/dist/templates/fullstack/web/src/vite-env.d.ts.template +1 -0
  183. package/dist/templates/fullstack/web/tsconfig.json.template +21 -0
  184. package/dist/templates/fullstack/web/tsconfig.node.json.template +11 -0
  185. package/dist/templates/fullstack/web/vite.config.ts.template +15 -0
  186. package/dist/templates/hosted/root/.env.local.template +13 -0
  187. package/dist/templates/hosted/root/.gitignore.template +32 -0
  188. package/dist/templates/hosted/root/CLAUDE.md.template +139 -0
  189. package/dist/templates/hosted/root/drizzle.config.ts.template +10 -0
  190. package/dist/templates/hosted/root/next.config.ts.template +15 -0
  191. package/dist/templates/hosted/root/package.json.template +40 -0
  192. package/dist/templates/hosted/root/postcss.config.mjs.template +9 -0
  193. package/dist/templates/hosted/root/primstack.config.json.template +5 -0
  194. package/dist/templates/hosted/root/tailwind.config.ts.template +14 -0
  195. package/dist/templates/hosted/root/tsconfig.json.template +25 -0
  196. package/dist/templates/hosted/root/wrangler.toml.template +9 -0
  197. package/dist/templates/hosted/src/app/actions/example.ts.template +50 -0
  198. package/dist/templates/hosted/src/app/api/health/route.ts.template +5 -0
  199. package/dist/templates/hosted/src/app/auth/login/page.tsx.template +32 -0
  200. package/dist/templates/hosted/src/app/globals.css.template +59 -0
  201. package/dist/templates/hosted/src/app/layout.tsx.template +24 -0
  202. package/dist/templates/hosted/src/app/page.tsx.template +34 -0
  203. package/dist/templates/hosted/src/db/migrations/0000_initial.sql.template +43 -0
  204. package/dist/templates/hosted/src/db/schema.ts.template +52 -0
  205. package/dist/templates/hosted/src/env.d.ts.template +10 -0
  206. package/dist/templates/hosted/src/instrumentation.ts.template +6 -0
  207. package/dist/templates/hosted/src/lib/auth.ts.template +35 -0
  208. package/dist/templates/hosted/src/lib/db.ts.template +17 -0
  209. package/dist/templates/hosted/src/middleware.ts.template +6 -0
  210. 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,10 @@
1
+ import { defineConfig } from 'drizzle-kit';
2
+
3
+ export default defineConfig({
4
+ schema: './src/db/schema.ts',
5
+ out: './src/db/migrations',
6
+ dialect: 'sqlite',
7
+ dbCredentials: {
8
+ url: process.env.DATABASE_URL ?? 'file:./local.db',
9
+ },
10
+ });
@@ -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,9 @@
1
+ /** @type {import('postcss-load-config').Config} */
2
+ const config = {
3
+ plugins: {
4
+ tailwindcss: {},
5
+ autoprefixer: {},
6
+ },
7
+ };
8
+
9
+ export default config;
@@ -0,0 +1,5 @@
1
+ {
2
+ "projectType": "hosted",
3
+ "personality": "{{PERSONALITY}}",
4
+ "componentPath": "src/components/ui"
5
+ }
@@ -0,0 +1,14 @@
1
+ import type { Config } from 'tailwindcss';
2
+
3
+ const config: Config = {
4
+ content: [
5
+ './src/**/*.{ts,tsx}',
6
+ './node_modules/@primstack/ui/**/*.js',
7
+ ],
8
+ theme: {
9
+ extend: {},
10
+ },
11
+ plugins: [],
12
+ };
13
+
14
+ export default config;
@@ -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,9 @@
1
+ name = "{{PROJECT_NAME}}"
2
+ compatibility_date = "2024-09-23"
3
+ compatibility_flags = ["nodejs_compat"]
4
+ pages_build_output_dir = ".vercel/output/static"
5
+
6
+ [[d1_databases]]
7
+ binding = "DB"
8
+ database_name = "{{PROJECT_NAME}}-db"
9
+ database_id = "local"
@@ -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,5 @@
1
+ export const runtime = 'edge';
2
+
3
+ export function GET() {
4
+ return Response.json({ ok: true, timestamp: Date.now() });
5
+ }
@@ -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,10 @@
1
+ interface CloudflareEnv {
2
+ DB: D1Database;
3
+ }
4
+
5
+ declare module '@cloudflare/next-on-pages' {
6
+ export function getRequestContext(): {
7
+ env: CloudflareEnv;
8
+ ctx: ExecutionContext;
9
+ };
10
+ }
@@ -0,0 +1,6 @@
1
+ export async function register() {
2
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
3
+ const { setupDevPlatform } = await import('@cloudflare/next-on-pages/next-dev');
4
+ await setupDevPlatform();
5
+ }
6
+ }
@@ -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
+ }
@@ -0,0 +1,6 @@
1
+ export { auth as middleware } from '@/lib/auth';
2
+
3
+ export const config = {
4
+ // Protect all routes under /dashboard, /api (except health and auth)
5
+ matcher: ['/dashboard/:path*'],
6
+ };