@getjack/jack 0.1.19 → 0.1.22
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/package.json +5 -2
- package/src/commands/down.ts +11 -1
- package/src/commands/init.ts +19 -6
- package/src/commands/new.ts +56 -4
- package/src/commands/publish.ts +1 -1
- package/src/lib/agents.ts +3 -1
- package/src/lib/auth/ensure-auth.test.ts +3 -3
- package/src/lib/control-plane.ts +15 -1
- package/src/lib/deploy-upload.ts +26 -1
- package/src/lib/hooks.ts +232 -1
- package/src/lib/managed-deploy.ts +13 -6
- package/src/lib/managed-down.ts +66 -45
- package/src/lib/progress.ts +76 -5
- package/src/lib/project-list.ts +6 -1
- package/src/lib/project-operations.ts +21 -31
- package/src/lib/project-resolver.ts +1 -1
- package/src/lib/zip-packager.ts +36 -7
- package/src/templates/index.ts +1 -1
- package/src/templates/types.ts +16 -0
- package/templates/CLAUDE.md +172 -5
- package/templates/miniapp/.jack.json +1 -3
- package/templates/saas/.jack.json +154 -0
- package/templates/saas/AGENTS.md +333 -0
- package/templates/saas/bun.lock +925 -0
- package/templates/saas/components.json +21 -0
- package/templates/saas/index.html +12 -0
- package/templates/saas/package.json +75 -0
- package/templates/saas/public/icon.png +0 -0
- package/templates/saas/public/og.png +0 -0
- package/templates/saas/schema.sql +73 -0
- package/templates/saas/src/auth.ts +77 -0
- package/templates/saas/src/client/App.tsx +63 -0
- package/templates/saas/src/client/components/ProtectedRoute.tsx +29 -0
- package/templates/saas/src/client/components/ThemeToggle.tsx +32 -0
- package/templates/saas/src/client/components/ui/accordion.tsx +62 -0
- package/templates/saas/src/client/components/ui/alert-dialog.tsx +133 -0
- package/templates/saas/src/client/components/ui/alert.tsx +60 -0
- package/templates/saas/src/client/components/ui/aspect-ratio.tsx +9 -0
- package/templates/saas/src/client/components/ui/avatar.tsx +39 -0
- package/templates/saas/src/client/components/ui/badge.tsx +39 -0
- package/templates/saas/src/client/components/ui/breadcrumb.tsx +102 -0
- package/templates/saas/src/client/components/ui/button-group.tsx +78 -0
- package/templates/saas/src/client/components/ui/button.tsx +60 -0
- package/templates/saas/src/client/components/ui/card.tsx +75 -0
- package/templates/saas/src/client/components/ui/carousel.tsx +228 -0
- package/templates/saas/src/client/components/ui/chart.tsx +326 -0
- package/templates/saas/src/client/components/ui/checkbox.tsx +29 -0
- package/templates/saas/src/client/components/ui/collapsible.tsx +19 -0
- package/templates/saas/src/client/components/ui/command.tsx +159 -0
- package/templates/saas/src/client/components/ui/context-menu.tsx +224 -0
- package/templates/saas/src/client/components/ui/dialog.tsx +127 -0
- package/templates/saas/src/client/components/ui/drawer.tsx +124 -0
- package/templates/saas/src/client/components/ui/dropdown-menu.tsx +226 -0
- package/templates/saas/src/client/components/ui/empty.tsx +94 -0
- package/templates/saas/src/client/components/ui/field.tsx +232 -0
- package/templates/saas/src/client/components/ui/form.tsx +152 -0
- package/templates/saas/src/client/components/ui/hover-card.tsx +38 -0
- package/templates/saas/src/client/components/ui/input-group.tsx +158 -0
- package/templates/saas/src/client/components/ui/input-otp.tsx +68 -0
- package/templates/saas/src/client/components/ui/input.tsx +21 -0
- package/templates/saas/src/client/components/ui/item.tsx +172 -0
- package/templates/saas/src/client/components/ui/kbd.tsx +28 -0
- package/templates/saas/src/client/components/ui/label.tsx +21 -0
- package/templates/saas/src/client/components/ui/menubar.tsx +250 -0
- package/templates/saas/src/client/components/ui/navigation-menu.tsx +161 -0
- package/templates/saas/src/client/components/ui/pagination.tsx +106 -0
- package/templates/saas/src/client/components/ui/popover.tsx +42 -0
- package/templates/saas/src/client/components/ui/progress.tsx +26 -0
- package/templates/saas/src/client/components/ui/radio-group.tsx +45 -0
- package/templates/saas/src/client/components/ui/resizable.tsx +46 -0
- package/templates/saas/src/client/components/ui/scroll-area.tsx +56 -0
- package/templates/saas/src/client/components/ui/select.tsx +173 -0
- package/templates/saas/src/client/components/ui/separator.tsx +28 -0
- package/templates/saas/src/client/components/ui/sheet.tsx +128 -0
- package/templates/saas/src/client/components/ui/sidebar.tsx +694 -0
- package/templates/saas/src/client/components/ui/skeleton.tsx +13 -0
- package/templates/saas/src/client/components/ui/slider.tsx +58 -0
- package/templates/saas/src/client/components/ui/sonner.tsx +38 -0
- package/templates/saas/src/client/components/ui/spinner.tsx +16 -0
- package/templates/saas/src/client/components/ui/switch.tsx +28 -0
- package/templates/saas/src/client/components/ui/table.tsx +90 -0
- package/templates/saas/src/client/components/ui/tabs.tsx +54 -0
- package/templates/saas/src/client/components/ui/textarea.tsx +18 -0
- package/templates/saas/src/client/components/ui/toggle-group.tsx +80 -0
- package/templates/saas/src/client/components/ui/toggle.tsx +44 -0
- package/templates/saas/src/client/components/ui/tooltip.tsx +57 -0
- package/templates/saas/src/client/hooks/use-mobile.ts +19 -0
- package/templates/saas/src/client/hooks/useAuth.ts +14 -0
- package/templates/saas/src/client/hooks/useSubscription.ts +86 -0
- package/templates/saas/src/client/index.css +165 -0
- package/templates/saas/src/client/lib/auth-client.ts +7 -0
- package/templates/saas/src/client/lib/plans.ts +82 -0
- package/templates/saas/src/client/lib/utils.ts +6 -0
- package/templates/saas/src/client/main.tsx +15 -0
- package/templates/saas/src/client/pages/DashboardPage.tsx +394 -0
- package/templates/saas/src/client/pages/ForgotPasswordPage.tsx +153 -0
- package/templates/saas/src/client/pages/HomePage.tsx +285 -0
- package/templates/saas/src/client/pages/LoginPage.tsx +169 -0
- package/templates/saas/src/client/pages/PricingPage.tsx +467 -0
- package/templates/saas/src/client/pages/ResetPasswordPage.tsx +200 -0
- package/templates/saas/src/client/pages/SignupPage.tsx +192 -0
- package/templates/saas/src/index.ts +208 -0
- package/templates/saas/tsconfig.json +18 -0
- package/templates/saas/vite.config.ts +14 -0
- package/templates/saas/wrangler.jsonc +20 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# Agent Context
|
|
2
|
+
|
|
3
|
+
## Infrastructure
|
|
4
|
+
|
|
5
|
+
This project uses **jack** for deployment. When Jack MCP is connected, prefer `mcp__jack__*` tools over CLI commands - they're cloud-aware and faster.
|
|
6
|
+
|
|
7
|
+
**Do NOT use wrangler directly.** Jack manages config and may use cloud-hosted resources where wrangler won't work.
|
|
8
|
+
|
|
9
|
+
Common operations:
|
|
10
|
+
- **Deploy**: `jack ship` or Jack MCP
|
|
11
|
+
- **Database**: `jack services db create` or Jack MCP
|
|
12
|
+
- **Status**: `jack status` or Jack MCP
|
|
13
|
+
|
|
14
|
+
## Project Structure
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
src/
|
|
18
|
+
├── index.ts # Hono API entry point
|
|
19
|
+
├── auth.ts # Better Auth configuration
|
|
20
|
+
├── client/ # React frontend
|
|
21
|
+
│ ├── App.tsx # Application entry
|
|
22
|
+
│ ├── main.tsx # React entry point
|
|
23
|
+
│ ├── index.css # Tailwind + theme CSS
|
|
24
|
+
│ ├── lib/
|
|
25
|
+
│ │ ├── auth-client.ts # Better Auth client
|
|
26
|
+
│ │ └── utils.ts # Utility functions (cn)
|
|
27
|
+
│ ├── hooks/
|
|
28
|
+
│ │ ├── useAuth.ts # Auth hook
|
|
29
|
+
│ │ └── useSubscription.ts # Subscription hook
|
|
30
|
+
│ ├── pages/ # Page components
|
|
31
|
+
│ │ ├── HomePage.tsx # Public landing page
|
|
32
|
+
│ │ ├── LoginPage.tsx # Login form
|
|
33
|
+
│ │ ├── SignupPage.tsx # Signup form
|
|
34
|
+
│ │ ├── DashboardPage.tsx # Protected dashboard
|
|
35
|
+
│ │ └── PricingPage.tsx # Pricing page
|
|
36
|
+
│ └── components/ # Reusable components
|
|
37
|
+
│ └── ui/ # shadcn/ui components
|
|
38
|
+
└── db/
|
|
39
|
+
└── schema.sql # D1 database schema
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Authentication (Better Auth)
|
|
43
|
+
|
|
44
|
+
This template uses Better Auth with email/password authentication.
|
|
45
|
+
|
|
46
|
+
### Server-side Setup (src/auth.ts)
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { betterAuth } from "better-auth";
|
|
50
|
+
import { stripe } from "@better-auth/stripe";
|
|
51
|
+
import { Kysely } from "kysely";
|
|
52
|
+
import { D1Dialect } from "kysely-d1";
|
|
53
|
+
|
|
54
|
+
export function createAuth(env: Env) {
|
|
55
|
+
// Use Kysely with D1 dialect - Better Auth uses Kysely internally for D1
|
|
56
|
+
const db = new Kysely<any>({
|
|
57
|
+
dialect: new D1Dialect({ database: env.DB }),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return betterAuth({
|
|
61
|
+
database: { db, type: "sqlite" },
|
|
62
|
+
emailAndPassword: { enabled: true },
|
|
63
|
+
secret: env.BETTER_AUTH_SECRET,
|
|
64
|
+
plugins: [stripe({ /* config */ })],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Client-side Usage
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { authClient } from './lib/auth-client';
|
|
73
|
+
|
|
74
|
+
// Sign up new user
|
|
75
|
+
await authClient.signUp.email({
|
|
76
|
+
email: 'user@example.com',
|
|
77
|
+
password: 'password123',
|
|
78
|
+
name: 'John Doe'
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Sign in existing user
|
|
82
|
+
await authClient.signIn.email({
|
|
83
|
+
email: 'user@example.com',
|
|
84
|
+
password: 'password123'
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Get current session
|
|
88
|
+
const { data: session } = await authClient.getSession();
|
|
89
|
+
if (session) {
|
|
90
|
+
console.log('Logged in as:', session.user.email);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Sign out
|
|
94
|
+
await authClient.signOut();
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Payments (Stripe)
|
|
98
|
+
|
|
99
|
+
Uses Better Auth's Stripe plugin for subscription management. Webhooks are handled automatically at `/api/auth/stripe/webhook`.
|
|
100
|
+
|
|
101
|
+
### Subscription Tiers
|
|
102
|
+
|
|
103
|
+
- **Free**: No payment required, basic features
|
|
104
|
+
- **Pro**: Monthly subscription, full features
|
|
105
|
+
- **Enterprise**: Monthly subscription, premium support
|
|
106
|
+
|
|
107
|
+
### Upgrading a User
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import { authClient } from './lib/auth-client';
|
|
111
|
+
|
|
112
|
+
async function handleUpgrade(plan: 'pro' | 'enterprise') {
|
|
113
|
+
const { data, error } = await authClient.subscription.upgrade({
|
|
114
|
+
plan,
|
|
115
|
+
successUrl: '/dashboard?upgraded=true',
|
|
116
|
+
cancelUrl: '/pricing',
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (data?.url) {
|
|
120
|
+
// Redirect to Stripe Checkout
|
|
121
|
+
window.location.href = data.url;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Checking Subscription Status
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import { authClient } from './lib/auth-client';
|
|
130
|
+
|
|
131
|
+
async function getCurrentPlan() {
|
|
132
|
+
const { data: subscriptions } = await authClient.subscription.list();
|
|
133
|
+
|
|
134
|
+
const activeSubscription = subscriptions?.find(
|
|
135
|
+
s => s.status === 'active' || s.status === 'trialing'
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
return activeSubscription?.plan || 'free';
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Adding New Pages
|
|
143
|
+
|
|
144
|
+
1. Create component in `src/client/pages/NewPage.tsx`
|
|
145
|
+
2. Add route in `src/client/App.tsx`
|
|
146
|
+
3. For protected pages, check session before rendering
|
|
147
|
+
|
|
148
|
+
### Protected Page Pattern
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { authClient } from '../lib/auth-client';
|
|
152
|
+
import { useEffect, useState } from 'react';
|
|
153
|
+
|
|
154
|
+
export function ProtectedPage() {
|
|
155
|
+
const [session, setSession] = useState(null);
|
|
156
|
+
const [loading, setLoading] = useState(true);
|
|
157
|
+
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
authClient.getSession().then(({ data }) => {
|
|
160
|
+
setSession(data);
|
|
161
|
+
setLoading(false);
|
|
162
|
+
});
|
|
163
|
+
}, []);
|
|
164
|
+
|
|
165
|
+
if (loading) return <div>Loading...</div>;
|
|
166
|
+
if (!session) {
|
|
167
|
+
window.location.href = '/login';
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return <div>Protected content for {session.user.email}</div>;
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Adding API Routes
|
|
176
|
+
|
|
177
|
+
API routes are defined in `src/index.ts` using Hono.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import { Hono } from "hono";
|
|
181
|
+
|
|
182
|
+
const app = new Hono<{ Bindings: Env }>();
|
|
183
|
+
|
|
184
|
+
// Public route
|
|
185
|
+
app.get("/api/health", (c) => c.json({ status: "ok" }));
|
|
186
|
+
|
|
187
|
+
// Protected route (check auth)
|
|
188
|
+
app.get("/api/me", async (c) => {
|
|
189
|
+
const auth = createAuth(c.env);
|
|
190
|
+
const session = await auth.api.getSession({ headers: c.req.raw.headers });
|
|
191
|
+
|
|
192
|
+
if (!session) {
|
|
193
|
+
return c.json({ error: "Unauthorized" }, 401);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return c.json({ user: session.user });
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Database Schema
|
|
201
|
+
|
|
202
|
+
The D1 database uses Better Auth's default schema (singular table names, camelCase columns):
|
|
203
|
+
|
|
204
|
+
- `user` - User accounts with email, name, stripeCustomerId
|
|
205
|
+
- `session` - Active user sessions
|
|
206
|
+
- `account` - OAuth provider accounts (includes password for email/password auth)
|
|
207
|
+
- `verification` - Email verification tokens
|
|
208
|
+
- `subscription` - Stripe subscription records
|
|
209
|
+
|
|
210
|
+
To modify the schema, edit `schema.sql` and run migrations with jack.
|
|
211
|
+
|
|
212
|
+
## Stripe Setup (Required Order)
|
|
213
|
+
|
|
214
|
+
### 1. Initial Deploy & Webhook Setup
|
|
215
|
+
|
|
216
|
+
The webhook secret requires the deployment URL, so follow this order:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
# 1. Deploy first (without webhook secret - subscriptions won't work yet)
|
|
220
|
+
jack ship
|
|
221
|
+
|
|
222
|
+
# 2. Get your deployment URL
|
|
223
|
+
jack status # e.g., https://your-app.runjack.xyz
|
|
224
|
+
|
|
225
|
+
# 3. Create webhook in Stripe Dashboard:
|
|
226
|
+
# - Go to: Developers → Webhooks → Add endpoint
|
|
227
|
+
# - URL: https://your-app.runjack.xyz/api/auth/stripe/webhook
|
|
228
|
+
# - Events: customer.subscription.created, customer.subscription.updated,
|
|
229
|
+
# customer.subscription.deleted, checkout.session.completed
|
|
230
|
+
# - Copy the signing secret (whsec_...)
|
|
231
|
+
|
|
232
|
+
# 4. Set the webhook secret
|
|
233
|
+
jack secrets set STRIPE_WEBHOOK_SECRET whsec_your_secret_here
|
|
234
|
+
|
|
235
|
+
# 5. Redeploy to enable Stripe plugin
|
|
236
|
+
jack ship
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Important:** The Stripe plugin is DISABLED if `STRIPE_WEBHOOK_SECRET` is missing. This prevents silent sync failures.
|
|
240
|
+
|
|
241
|
+
### 2. Customer Portal Setup
|
|
242
|
+
|
|
243
|
+
For subscription upgrades/downgrades to work:
|
|
244
|
+
|
|
245
|
+
1. Go to [Stripe Dashboard → Settings → Billing → Customer Portal](https://dashboard.stripe.com/settings/billing/portal)
|
|
246
|
+
2. Enable **"Customers can switch plans"**
|
|
247
|
+
3. Add your products/prices to the allowed list
|
|
248
|
+
4. Enable **"Customers can cancel subscriptions"** if you want in-portal cancellation
|
|
249
|
+
|
|
250
|
+
Without this, users will see: *"This subscription cannot be updated because the subscription update feature in the portal configuration is disabled."*
|
|
251
|
+
|
|
252
|
+
## Environment Variables
|
|
253
|
+
|
|
254
|
+
Required secrets (set via `jack secrets set KEY value`):
|
|
255
|
+
|
|
256
|
+
| Secret | Description | Where to get it |
|
|
257
|
+
|--------|-------------|-----------------|
|
|
258
|
+
| `BETTER_AUTH_SECRET` | Random secret for auth tokens | `openssl rand -base64 32` |
|
|
259
|
+
| `STRIPE_SECRET_KEY` | Stripe API secret key | [Stripe Dashboard](https://dashboard.stripe.com/apikeys) |
|
|
260
|
+
| `STRIPE_WEBHOOK_SECRET` | Webhook signing secret (whsec_...) | Created after adding webhook |
|
|
261
|
+
| `STRIPE_PRO_PRICE_ID` | Price ID for Pro plan | [Stripe Products](https://dashboard.stripe.com/products) |
|
|
262
|
+
| `STRIPE_ENTERPRISE_PRICE_ID` | Price ID for Enterprise plan | [Stripe Products](https://dashboard.stripe.com/products) |
|
|
263
|
+
|
|
264
|
+
## SQL Execution
|
|
265
|
+
|
|
266
|
+
Jack supports secure SQL execution against D1 databases:
|
|
267
|
+
|
|
268
|
+
**Via MCP** (preferred for agents):
|
|
269
|
+
- `execute_sql({ sql: "SELECT * FROM user" })` - read queries work by default
|
|
270
|
+
- `execute_sql({ sql: "INSERT...", allow_write: true })` - writes require allow_write
|
|
271
|
+
- Destructive ops (DROP, TRUNCATE, ALTER) are blocked via MCP - use CLI
|
|
272
|
+
|
|
273
|
+
**Via CLI**:
|
|
274
|
+
- `jack services db execute "SELECT * FROM user"` - read queries
|
|
275
|
+
- `jack services db execute "INSERT..." --write` - write queries
|
|
276
|
+
- `jack services db execute --file schema.sql --write` - run SQL from file
|
|
277
|
+
|
|
278
|
+
## Customizing Theme
|
|
279
|
+
|
|
280
|
+
The template uses shadcn/ui CSS variables for theming. To customize:
|
|
281
|
+
|
|
282
|
+
1. Visit [ui.shadcn.com/create](https://ui.shadcn.com/create)
|
|
283
|
+
2. Design your theme with the visual editor
|
|
284
|
+
3. Copy the generated CSS variables
|
|
285
|
+
4. Replace the `:root` and `.dark` blocks in `src/client/index.css`
|
|
286
|
+
|
|
287
|
+
The default theme uses neutral colors. The CSS variables control all component colors:
|
|
288
|
+
|
|
289
|
+
```css
|
|
290
|
+
:root {
|
|
291
|
+
--background: 0 0% 100%;
|
|
292
|
+
--foreground: 0 0% 3.9%;
|
|
293
|
+
--primary: 0 0% 9%;
|
|
294
|
+
--primary-foreground: 0 0% 98%;
|
|
295
|
+
/* ... etc */
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## External API Integration Principles
|
|
300
|
+
|
|
301
|
+
### 1. Don't trust cached data for real-time state
|
|
302
|
+
|
|
303
|
+
Libraries that sync external data via webhooks (like Better Auth's Stripe plugin) provide **eventual consistency**, not real-time truth. Webhooks may be delayed, fail silently, or not be configured yet.
|
|
304
|
+
|
|
305
|
+
**Rule**: For user-facing state that must be accurate (subscription status, payment state, cancellation), query the source API directly rather than relying on locally cached data.
|
|
306
|
+
|
|
307
|
+
### 2. Defensive coding for external data
|
|
308
|
+
|
|
309
|
+
External APIs return unpredictable shapes. Fields you expect may be null, undefined, or have different types in edge cases.
|
|
310
|
+
|
|
311
|
+
**Rule**: Always add null checks before transforming external data, especially timestamps, nested objects, and optional fields.
|
|
312
|
+
|
|
313
|
+
### 3. Return actionable error details
|
|
314
|
+
|
|
315
|
+
Generic "500 Internal Server Error" messages waste debugging time. During development, include the actual error.
|
|
316
|
+
|
|
317
|
+
**Rule**: In catch blocks, return `details: err.message` so you can see what actually failed. Strip these in production if needed.
|
|
318
|
+
|
|
319
|
+
### 4. Test the unhappy paths
|
|
320
|
+
|
|
321
|
+
The happy path (user signs up, subscribes, uses app) usually works. The bugs hide in: cancellation flows, expired states, partial failures, re-subscription after cancel.
|
|
322
|
+
|
|
323
|
+
**Rule**: Manually test state transitions, not just initial states.
|
|
324
|
+
|
|
325
|
+
## Resources
|
|
326
|
+
|
|
327
|
+
- [Better Auth Docs](https://www.betterauth.com/docs)
|
|
328
|
+
- [Better Auth Stripe Plugin](https://www.betterauth.com/docs/plugins/stripe)
|
|
329
|
+
- [Stripe Billing Docs](https://docs.stripe.com/billing)
|
|
330
|
+
- [Stripe Webhooks](https://docs.stripe.com/billing/subscriptions/webhooks)
|
|
331
|
+
- [shadcn/ui Theme Creator](https://ui.shadcn.com/create)
|
|
332
|
+
- [Hono Documentation](https://hono.dev)
|
|
333
|
+
- [Cloudflare D1 Docs](https://developers.cloudflare.com/d1)
|