mirror of
https://gh-proxy.org/https://github.com/klinsmaya/tinyship-skills.git
synced 2026-11-08 01:06:53 +08:00
Add tinyship-cloudflare skill for all-in Cloudflare setup
New skill that guides users through a complete Cloudflare stack setup: - Workers deployment (TanStack Start) - D1 database or Hyperdrive+PostgreSQL (user chooses) - R2 object storage - Email Sending (REST API) - Turnstile bot protection - Production deployment with secrets management Includes 6 reference docs for detailed per-service setup steps. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -13,6 +13,7 @@ When a user asks for help with their TinyShip project, direct them to the releva
|
||||
- **Deployment**: `skills/tinyship-deploy/SKILL.md` — Docker, Vercel, Cloudflare Workers, VPS
|
||||
- **New Features**: `skills/tinyship-feature/SKILL.md` — monorepo feature development checklist
|
||||
- **AI Integration**: `skills/tinyship-ai/SKILL.md` — chat, image gen, video gen providers
|
||||
- **Cloudflare All-in**: `skills/tinyship-cloudflare/SKILL.md` — Workers + D1/Hyperdrive + R2 + Email + Turnstile
|
||||
|
||||
## Skill Format
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ npx skills add TinyshipCN/tinyship-skills --list
|
||||
| **tinyship-feature** | New feature development — guided monorepo feature workflow with libs, config, i18n, permissions |
|
||||
| **tinyship-deploy** | Deployment guide — Docker, Vercel, Cloudflare Workers, VPS/PM2 |
|
||||
| **tinyship-ai** | AI feature integration — chat, image generation, video generation with credit system |
|
||||
| **tinyship-cloudflare** | All-in Cloudflare setup — Workers + D1/Hyperdrive + R2 + Email Sending + Turnstile |
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -42,6 +43,7 @@ After installation, just ask your AI agent naturally:
|
||||
- *"Add Stripe payment"* → triggers `tinyship-payment`
|
||||
- *"Deploy to Docker"* → triggers `tinyship-deploy`
|
||||
- *"Add a new feature for user profiles"* → triggers `tinyship-feature`
|
||||
- *"Set up Cloudflare for my project"* → triggers `tinyship-cloudflare`
|
||||
|
||||
## Supported Agents
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
---
|
||||
name: tinyship-cloudflare
|
||||
description: >-
|
||||
One-stop Cloudflare setup for TinyShip: Workers + D1/Hyperdrive + R2 + Email Sending +
|
||||
Turnstile. Use when the user asks to "set up cloudflare", "all-in cloudflare", "deploy to
|
||||
workers", "configure d1", "configure r2", "cf setup", "cloudflare full stack", or wants
|
||||
to use the Cloudflare ecosystem for their TinyShip project.
|
||||
---
|
||||
|
||||
# TinyShip Cloudflare Setup
|
||||
|
||||
One-stop wizard to configure the full Cloudflare stack for TinyShip (TanStack Start).
|
||||
|
||||
**This is a conversational, step-by-step wizard. You MUST ask the user at each
|
||||
decision point and wait for their answer before proceeding. NEVER make choices
|
||||
on the user's behalf.**
|
||||
|
||||
> **Scope:** This skill configures TanStack Start (`apps/tanstack-app`) for Cloudflare
|
||||
> Workers deployment. Next.js and Nuxt.js are designed for Node.js environments and
|
||||
> do not deploy to Workers. If the user hasn't chosen TanStack Start yet, suggest
|
||||
> running the `tinyship-setup` skill first.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before starting, verify:
|
||||
|
||||
1. **Node.js >= 22.20.0** — run `node -v`
|
||||
2. **pnpm >= 9.0.0** — run `pnpm -v`
|
||||
3. **Dependencies installed** — if `node_modules/` is missing, run `pnpm install`
|
||||
4. **A Cloudflare account** — https://dash.cloudflare.com/sign-up
|
||||
|
||||
## Step 1: Wrangler Login
|
||||
|
||||
Run the following to authenticate with Cloudflare:
|
||||
|
||||
```bash
|
||||
cd apps/tanstack-app
|
||||
npx wrangler login
|
||||
```
|
||||
|
||||
This opens a browser for authorization. After success, verify:
|
||||
|
||||
```bash
|
||||
npx wrangler whoami
|
||||
```
|
||||
|
||||
Capture the **Account ID** from the output — it will be reused for R2 and Email.
|
||||
|
||||
If the user is already logged in (`wrangler whoami` shows account info), skip this step
|
||||
and tell the user.
|
||||
|
||||
## Step 2: Database — D1 or Hyperdrive
|
||||
|
||||
**⛔ STOP — You MUST ask the user before proceeding.**
|
||||
|
||||
Present these options and wait for the user's response:
|
||||
|
||||
| Option | Database | Best For |
|
||||
|--------|----------|----------|
|
||||
| **A** | **Cloudflare D1** (SQLite) | All-in Cloudflare, zero external deps, low cost |
|
||||
| **B** | **Hyperdrive + PostgreSQL** | Reuse existing PG, high write concurrency, data parity with Next/Nuxt |
|
||||
|
||||
Explain the trade-offs:
|
||||
- **D1**: Fully managed, included in Workers billing, 5-10 GB limit, single-writer constraint
|
||||
- **Hyperdrive**: Needs an external PostgreSQL, but no storage limits, same DB across all 3 apps
|
||||
|
||||
### If the user chooses D1:
|
||||
|
||||
Read and follow [references/d1-setup.md](references/d1-setup.md).
|
||||
|
||||
### If the user chooses Hyperdrive:
|
||||
|
||||
Read and follow [references/hyperdrive-setup.md](references/hyperdrive-setup.md).
|
||||
|
||||
**After database is configured, verify locally:**
|
||||
|
||||
```bash
|
||||
cd apps/tanstack-app
|
||||
pnpm dev:cf
|
||||
```
|
||||
|
||||
Check that the app loads and database-dependent pages work. Tell the user the results.
|
||||
|
||||
## Step 3: R2 Storage (Optional)
|
||||
|
||||
**⛔ STOP — You MUST ask the user before proceeding.**
|
||||
|
||||
Ask: **Do you want to set up Cloudflare R2 for file storage (images, uploads)?**
|
||||
|
||||
- **Yes** — read and follow [references/r2-setup.md](references/r2-setup.md)
|
||||
- **No** — skip (can be configured later)
|
||||
|
||||
## Step 4: Email Sending (Optional)
|
||||
|
||||
**⛔ STOP — You MUST ask the user before proceeding.**
|
||||
|
||||
Ask: **Do you want to use Cloudflare Email Sending for transactional emails
|
||||
(verification codes, password resets)?**
|
||||
|
||||
- **Yes** — read and follow [references/email-setup.md](references/email-setup.md)
|
||||
- **No** — skip (default is Resend, or configure later)
|
||||
|
||||
## Step 5: Turnstile Bot Protection (Optional)
|
||||
|
||||
**⛔ STOP — You MUST ask the user before proceeding.**
|
||||
|
||||
Ask: **Do you want to enable Cloudflare Turnstile (CAPTCHA) for login/signup protection?**
|
||||
|
||||
- **Yes** — read and follow [references/turnstile-setup.md](references/turnstile-setup.md)
|
||||
- **No** — skip (disabled by default, safe to add later)
|
||||
|
||||
## Step 6: Deploy to Workers
|
||||
|
||||
**⛔ STOP — You MUST ask the user before proceeding.**
|
||||
|
||||
Ask: **Do you want to deploy to Cloudflare Workers now, or just finish the local setup?**
|
||||
|
||||
- **Deploy now** — read and follow [references/workers-deploy.md](references/workers-deploy.md)
|
||||
- **Later** — tell the user they can deploy anytime with `cd apps/tanstack-app && pnpm run deploy:cf`
|
||||
|
||||
## Step 7: Summary
|
||||
|
||||
After setup is complete, provide a summary of what was configured:
|
||||
|
||||
```
|
||||
✅ Cloudflare Setup Complete
|
||||
─────────────────────────────
|
||||
Workers: TanStack Start on Cloudflare Workers
|
||||
Database: [D1 / Hyperdrive+PG] — configured and verified
|
||||
Storage: [R2 / skipped]
|
||||
Email: [Cloudflare Email / skipped]
|
||||
Turnstile: [enabled / skipped]
|
||||
Deployment: [deployed to <url> / ready for deploy]
|
||||
|
||||
Next steps:
|
||||
- Run `cd apps/tanstack-app && pnpm dev:cf` for local CF dev
|
||||
- Run `cd apps/tanstack-app && pnpm run deploy:cf` to deploy
|
||||
- Use `npx wrangler tail` to view live logs
|
||||
```
|
||||
|
||||
## Reference Files in TinyShip Repo
|
||||
|
||||
When executing this skill, read these files from the user's TinyShip project for context:
|
||||
|
||||
- `apps/tanstack-app/wrangler.jsonc` — current Wrangler config
|
||||
- `apps/tanstack-app/CF-NOTES.md` — known Cloudflare pitfalls
|
||||
- `apps/tanstack-app/.dev.vars.example` — local dev vars template
|
||||
- `apps/tanstack-app/vite.config.ts` — CF plugin conditional loading
|
||||
- `docs/user-guide/deployment/cloudflare-workers.md` — full deployment guide
|
||||
- `config/storage.ts` — R2 config structure
|
||||
- `config/email.ts` — Email config structure
|
||||
- `config/captcha.ts` — Turnstile config structure
|
||||
- `env.example` — all environment variables
|
||||
@@ -0,0 +1,128 @@
|
||||
# D1 Database Setup
|
||||
|
||||
## 1. Create D1 Database
|
||||
|
||||
```bash
|
||||
cd apps/tanstack-app
|
||||
npx wrangler d1 create tinyship-db
|
||||
```
|
||||
|
||||
Capture the returned `database_id` from the output.
|
||||
|
||||
## 2. Configure wrangler.jsonc
|
||||
|
||||
Edit `apps/tanstack-app/wrangler.jsonc`:
|
||||
|
||||
1. **Set `DB_DIALECT` to `d1`** in the `vars` section
|
||||
2. **Add the `d1_databases` binding** with the database ID from step 1
|
||||
3. **Remove or comment out the `hyperdrive` block** — D1 and Hyperdrive should not be active simultaneously
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"vars": {
|
||||
"DB_DIALECT": "d1",
|
||||
"APP_BASE_URL": "http://localhost:7001",
|
||||
"BETTER_AUTH_URL": "http://localhost:7001"
|
||||
},
|
||||
"d1_databases": [
|
||||
{
|
||||
"binding": "DB",
|
||||
"database_name": "tinyship-db",
|
||||
"database_id": "<paste-your-database-id-here>",
|
||||
"migrations_dir": "../../libs/database/drizzle-sqlite"
|
||||
}
|
||||
]
|
||||
// Remove or comment out any "hyperdrive" block
|
||||
}
|
||||
```
|
||||
|
||||
**Important:** The `migrations_dir` must point to `../../libs/database/drizzle-sqlite` —
|
||||
this is where Drizzle generates SQLite/D1 migration files.
|
||||
|
||||
## 3. Generate and Apply Migrations
|
||||
|
||||
```bash
|
||||
# From the monorepo root — generate SQLite migration files
|
||||
pnpm db:generate:sqlite
|
||||
|
||||
# From apps/tanstack-app — apply migrations to remote D1
|
||||
cd apps/tanstack-app
|
||||
npx wrangler d1 migrations apply tinyship-db
|
||||
|
||||
# For local D1 preview environment
|
||||
npx wrangler d1 migrations apply tinyship-db --local
|
||||
```
|
||||
|
||||
## 4. Seed Data (Optional)
|
||||
|
||||
D1 cannot use `pnpm db:seed` directly (the seed script uses `better-sqlite3` which
|
||||
cannot connect to D1). Two approaches:
|
||||
|
||||
### Option A: Register through the app (recommended for production)
|
||||
|
||||
Deploy first, then register accounts through the app UI. No pre-seeding needed.
|
||||
|
||||
### Option B: Export from local SQLite and import to D1
|
||||
|
||||
```bash
|
||||
# 1. From monorepo root — seed local SQLite
|
||||
mkdir -p data
|
||||
pnpm db:push:sqlite
|
||||
pnpm db:seed:sqlite
|
||||
|
||||
# 2. Export INSERT statements
|
||||
{
|
||||
echo "PRAGMA foreign_keys=OFF;"
|
||||
sqlite3 data/local.sqlite '.mode insert' '.dump user' | grep '^INSERT'
|
||||
sqlite3 data/local.sqlite '.mode insert' '.dump account' | grep '^INSERT'
|
||||
sqlite3 data/local.sqlite '.mode insert' '.dump blog_post' | grep '^INSERT'
|
||||
} > data/seed-data.sql
|
||||
|
||||
# 3. Import to remote D1
|
||||
cd apps/tanstack-app
|
||||
npx wrangler d1 execute tinyship-db --remote --file=../../data/seed-data.sql
|
||||
|
||||
# 4. Verify
|
||||
npx wrangler d1 execute tinyship-db --remote --command "SELECT email, name, role FROM user"
|
||||
```
|
||||
|
||||
## 5. Configure .dev.vars
|
||||
|
||||
Copy the template and set required secrets for local CF development:
|
||||
|
||||
```bash
|
||||
cd apps/tanstack-app
|
||||
cp .dev.vars.example .dev.vars
|
||||
```
|
||||
|
||||
Edit `.dev.vars`:
|
||||
```
|
||||
APP_BASE_URL=http://localhost:7001
|
||||
BETTER_AUTH_URL=http://localhost:7001
|
||||
BETTER_AUTH_SECRET=<your-auth-secret>
|
||||
```
|
||||
|
||||
When using D1, you do NOT need `DATABASE_URL` in `.dev.vars` — the D1 binding
|
||||
handles database access automatically.
|
||||
|
||||
## 6. Verify Locally
|
||||
|
||||
```bash
|
||||
cd apps/tanstack-app
|
||||
pnpm dev:cf
|
||||
```
|
||||
|
||||
The app should start on the displayed port. Verify:
|
||||
1. Home page loads
|
||||
2. Login works (if data was seeded)
|
||||
3. Check browser console and terminal for errors
|
||||
|
||||
## D1 Limitations
|
||||
|
||||
| Limit | Value |
|
||||
|-------|-------|
|
||||
| Storage | 5 GB (free) / 10 GB (paid) |
|
||||
| Max row size | 1 MB |
|
||||
| Write concurrency | Single writer |
|
||||
| No JSONB | Use `text` + JSON serialization (already handled) |
|
||||
| No PG operators | `->`, `->>`, `@>` not available (already handled) |
|
||||
@@ -0,0 +1,95 @@
|
||||
# Cloudflare Email Sending Setup
|
||||
|
||||
TinyShip uses Cloudflare's Email Sending REST API for transactional emails
|
||||
(verification codes, password resets, etc.). This is NOT Email Routing or
|
||||
Workers email bindings — it's a simple HTTP API that works from any environment.
|
||||
|
||||
## 1. Enable Email Sending
|
||||
|
||||
In the Cloudflare Dashboard:
|
||||
|
||||
1. Go to **Email** → **Email Sending**
|
||||
2. Add and verify a sending domain (e.g., `yourdomain.com`)
|
||||
3. Follow the DNS record setup (SPF, DKIM, DMARC)
|
||||
4. Wait for domain verification to complete
|
||||
|
||||
## 2. Create API Token
|
||||
|
||||
1. Go to **My Profile** → **API Tokens** → **Create Token**
|
||||
2. Use **Custom token** with the permission: **Account / Email Routing Addresses: Edit**
|
||||
(this also covers Email Sending)
|
||||
3. Save the token
|
||||
|
||||
Or reuse an existing `CLOUDFLARE_API_TOKEN` that has email permissions.
|
||||
|
||||
## 3. Configure Environment Variables
|
||||
|
||||
Add to your `.env` (monorepo root):
|
||||
|
||||
```env
|
||||
CLOUDFLARE_ACCOUNT_ID=<your-account-id>
|
||||
CLOUDFLARE_API_TOKEN=<api-token-with-email-permission>
|
||||
EMAIL_DEFAULT_FROM=[email protected]
|
||||
```
|
||||
|
||||
The `EMAIL_DEFAULT_FROM` must be an address on the domain you verified in step 1.
|
||||
|
||||
`CLOUDFLARE_ACCOUNT_ID` is shared with R2 — if already set, no need to add it again.
|
||||
|
||||
## 4. Switch Default Email Provider
|
||||
|
||||
Edit `config/email.ts` in the TinyShip project:
|
||||
|
||||
```typescript
|
||||
defaultProvider: 'cloudflare', // Change from 'resend' to 'cloudflare'
|
||||
```
|
||||
|
||||
This is a code change, not just an env var. The agent should make this edit.
|
||||
|
||||
## 5. For Workers Deployment
|
||||
|
||||
Add email credentials as wrangler secrets:
|
||||
|
||||
```bash
|
||||
cd apps/tanstack-app
|
||||
npx wrangler secret put CLOUDFLARE_API_TOKEN
|
||||
npx wrangler secret put EMAIL_DEFAULT_FROM
|
||||
```
|
||||
|
||||
`CLOUDFLARE_ACCOUNT_ID` can go in `wrangler.jsonc` `vars` (not sensitive):
|
||||
|
||||
```jsonc
|
||||
"vars": {
|
||||
"CLOUDFLARE_ACCOUNT_ID": "<your-account-id>"
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Verify
|
||||
|
||||
Run the built-in email test:
|
||||
|
||||
```bash
|
||||
pnpm email:test -- verification --to [email protected] --provider cloudflare
|
||||
```
|
||||
|
||||
This sends a test verification email using the Cloudflare provider.
|
||||
|
||||
## How It Works
|
||||
|
||||
The Cloudflare email provider (`libs/email/providers/cloudflare.ts`) sends emails via:
|
||||
|
||||
```
|
||||
POST https://api.cloudflare.com/client/v4/accounts/{accountId}/email/sending/send
|
||||
```
|
||||
|
||||
Email templates are precompiled from MJML to HTML at build time via
|
||||
`pnpm email:compile`. This is important for Workers — MJML cannot run at
|
||||
runtime in the Workers environment.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| 403 Forbidden | API token lacks email permission |
|
||||
| Domain not verified | Complete DNS setup in Dashboard → Email → Email Sending |
|
||||
| MJML error on Workers | Run `pnpm email:compile` before deploying |
|
||||
@@ -0,0 +1,119 @@
|
||||
# Hyperdrive + PostgreSQL Setup
|
||||
|
||||
Hyperdrive is Cloudflare's database acceleration proxy that enables Workers to connect
|
||||
to your existing PostgreSQL database via connection pooling and edge caching.
|
||||
|
||||
## 1. Prepare PostgreSQL Connection String
|
||||
|
||||
You need a PostgreSQL database accessible from the internet. The connection string format:
|
||||
|
||||
```
|
||||
postgresql://user:password@host:5432/dbname
|
||||
```
|
||||
|
||||
Common providers: Supabase, Neon, Railway, AWS RDS, self-hosted with public IP.
|
||||
|
||||
**Auto-detect local PostgreSQL:**
|
||||
|
||||
Before asking the user for a connection string, check if PostgreSQL is running locally:
|
||||
|
||||
```bash
|
||||
pg_isready 2>/dev/null || \
|
||||
/opt/homebrew/opt/postgresql@*/bin/pg_isready 2>/dev/null || \
|
||||
/usr/local/opt/postgresql@*/bin/pg_isready 2>/dev/null
|
||||
```
|
||||
|
||||
If running locally, the local connection string can be used for `localConnectionString`
|
||||
in wrangler config (for local CF dev). The remote Hyperdrive still needs an
|
||||
internet-accessible PostgreSQL for production deployment.
|
||||
|
||||
## 2. Create Hyperdrive Configuration
|
||||
|
||||
```bash
|
||||
cd apps/tanstack-app
|
||||
npx wrangler hyperdrive create tinyship-db \
|
||||
--connection-string="postgresql://user:password@host:5432/dbname"
|
||||
```
|
||||
|
||||
Capture the returned **Hyperdrive ID** from the output.
|
||||
|
||||
## 3. Configure wrangler.jsonc
|
||||
|
||||
Edit `apps/tanstack-app/wrangler.jsonc`:
|
||||
|
||||
1. **Add the `hyperdrive` binding** with the ID from step 2
|
||||
2. **Set `localConnectionString`** for local CF development (your local PG)
|
||||
3. **Remove or comment out `d1_databases` block** and `DB_DIALECT` var
|
||||
4. **Remove `DB_DIALECT` from `vars`** (defaults to `pg`)
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"hyperdrive": [
|
||||
{
|
||||
"binding": "HYPERDRIVE",
|
||||
"id": "<your-hyperdrive-id>",
|
||||
"localConnectionString": "postgresql://youruser@localhost:5432/tinyship"
|
||||
}
|
||||
],
|
||||
"vars": {
|
||||
"APP_BASE_URL": "http://localhost:7001",
|
||||
"BETTER_AUTH_URL": "http://localhost:7001"
|
||||
}
|
||||
// Remove or comment out "d1_databases" block
|
||||
// Remove "DB_DIALECT": "d1" from vars (pg is default)
|
||||
}
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
- **Production**: Workers use the Hyperdrive proxy (the `id`) to reach your remote PG
|
||||
- **Local `pnpm dev:cf`**: Wrangler uses `localConnectionString` to connect directly
|
||||
to your local database, avoiding network latency during development
|
||||
- **Fallback**: If Hyperdrive binding is unavailable, the code falls back to `DATABASE_URL`
|
||||
|
||||
## 4. Configure .dev.vars
|
||||
|
||||
```bash
|
||||
cd apps/tanstack-app
|
||||
cp .dev.vars.example .dev.vars
|
||||
```
|
||||
|
||||
Edit `.dev.vars`:
|
||||
```
|
||||
APP_BASE_URL=http://localhost:7001
|
||||
BETTER_AUTH_URL=http://localhost:7001
|
||||
BETTER_AUTH_SECRET=<your-auth-secret>
|
||||
DATABASE_URL=postgresql://youruser@localhost:5432/tinyship
|
||||
```
|
||||
|
||||
The `DATABASE_URL` here serves as a fallback when Hyperdrive is not available.
|
||||
|
||||
## 5. Push Schema and Seed
|
||||
|
||||
If the database is fresh, push schema and seed from the monorepo root:
|
||||
|
||||
```bash
|
||||
# From monorepo root
|
||||
pnpm db:push
|
||||
pnpm db:seed
|
||||
```
|
||||
|
||||
## 6. Verify Locally
|
||||
|
||||
```bash
|
||||
cd apps/tanstack-app
|
||||
pnpm dev:cf
|
||||
```
|
||||
|
||||
Verify:
|
||||
1. Home page loads
|
||||
2. Login with `[email protected]` / `admin123` works
|
||||
3. Dashboard shows data from PostgreSQL
|
||||
4. No `TCP connection` errors in the terminal
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| `Cannot read properties of undefined (reading 'connect')` | Hyperdrive not configured; check `wrangler.jsonc` binding |
|
||||
| Connection timeout in local dev | Verify `localConnectionString` points to a running PG |
|
||||
| `CF_FORCE_DIRECT_DB=1` | Debug flag to skip Hyperdrive and use `DATABASE_URL` directly |
|
||||
@@ -0,0 +1,100 @@
|
||||
# R2 Storage Setup
|
||||
|
||||
Cloudflare R2 is S3-compatible object storage. TinyShip uses the S3 HTTP API
|
||||
(not Workers bindings), so R2 works across all three apps — not just Workers.
|
||||
|
||||
## 1. Create R2 Bucket
|
||||
|
||||
In the Cloudflare Dashboard:
|
||||
|
||||
1. Go to **R2 Object Storage** → **Create bucket**
|
||||
2. Name it (e.g., `tinyship`) — this becomes the `R2_BUCKET` value
|
||||
3. Choose a location hint (optional)
|
||||
|
||||
Or via CLI:
|
||||
|
||||
```bash
|
||||
npx wrangler r2 bucket create tinyship
|
||||
```
|
||||
|
||||
## 2. Create R2 API Token
|
||||
|
||||
R2 uses separate S3-compatible credentials (not `CLOUDFLARE_API_TOKEN`):
|
||||
|
||||
1. Go to **R2 Object Storage** → **Manage R2 API tokens**
|
||||
2. Click **Create API token**
|
||||
3. Permissions: **Object Read & Write**
|
||||
4. Specify the bucket (or all buckets)
|
||||
5. Save the **Access Key ID** and **Secret Access Key**
|
||||
|
||||
## 3. Configure Environment Variables
|
||||
|
||||
Add to your `.env` (monorepo root):
|
||||
|
||||
```env
|
||||
STORAGE_PROVIDER=r2
|
||||
CLOUDFLARE_ACCOUNT_ID=<your-account-id>
|
||||
R2_ACCESS_KEY_ID=<r2-api-token-access-key-id>
|
||||
R2_ACCESS_KEY_SECRET=<r2-api-token-secret-access-key>
|
||||
R2_BUCKET=tinyship
|
||||
```
|
||||
|
||||
The `CLOUDFLARE_ACCOUNT_ID` is shared with Email Sending — if already set, no need
|
||||
to add it again.
|
||||
|
||||
### Optional: Public URL
|
||||
|
||||
If you set up a custom domain for your R2 bucket (Dashboard → R2 → bucket → Settings
|
||||
→ Custom Domains), add:
|
||||
|
||||
```env
|
||||
R2_PUBLIC_URL=https://cdn.yourdomain.com
|
||||
```
|
||||
|
||||
This enables permanent public URLs instead of time-limited signed URLs.
|
||||
|
||||
## 4. For Workers Deployment
|
||||
|
||||
When deploying to Workers, add R2 credentials as secrets:
|
||||
|
||||
```bash
|
||||
cd apps/tanstack-app
|
||||
npx wrangler secret put R2_ACCESS_KEY_ID
|
||||
npx wrangler secret put R2_ACCESS_KEY_SECRET
|
||||
npx wrangler secret put R2_BUCKET
|
||||
```
|
||||
|
||||
Or set them in `wrangler.jsonc` `vars` (for non-sensitive values like bucket name):
|
||||
|
||||
```jsonc
|
||||
"vars": {
|
||||
"STORAGE_PROVIDER": "r2",
|
||||
"R2_BUCKET": "tinyship"
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Verify
|
||||
|
||||
Test the upload endpoint:
|
||||
|
||||
```bash
|
||||
# Start the app
|
||||
pnpm dev # or pnpm dev:cf for Workers mode
|
||||
|
||||
# Try uploading via the UI at http://localhost:7001/{lang}/upload
|
||||
```
|
||||
|
||||
Check that files appear in your R2 bucket in the Cloudflare Dashboard.
|
||||
|
||||
## How It Works
|
||||
|
||||
R2 is accessed via S3-compatible HTTP API in `libs/storage/providers/s3.ts`:
|
||||
|
||||
```
|
||||
Endpoint: https://{accountId}.r2.cloudflarestorage.com
|
||||
Region: auto
|
||||
ForcePathStyle: true
|
||||
```
|
||||
|
||||
The `createR2Provider()` function builds an S3 client pointed at R2's endpoint.
|
||||
No Workers bindings are used — this works identically on Node.js and Workers.
|
||||
@@ -0,0 +1,81 @@
|
||||
# Turnstile Bot Protection Setup
|
||||
|
||||
Cloudflare Turnstile is a CAPTCHA alternative that protects login, signup,
|
||||
and password reset forms from bots.
|
||||
|
||||
## 1. Create Turnstile Widget
|
||||
|
||||
In the Cloudflare Dashboard:
|
||||
|
||||
1. Go to **Turnstile** → **Add Widget**
|
||||
2. Name: e.g., "TinyShip"
|
||||
3. Add your domains (e.g., `yourdomain.com`, `localhost`)
|
||||
4. Widget mode: **Managed** (recommended)
|
||||
5. Save and copy the **Site Key** and **Secret Key**
|
||||
|
||||
## 2. Configure Environment Variables
|
||||
|
||||
Add to your `.env` (monorepo root):
|
||||
|
||||
```env
|
||||
TURNSTILE_SECRET_KEY=<your-secret-key>
|
||||
NEXT_PUBLIC_TURNSTILE_SITE_KEY=<your-site-key>
|
||||
```
|
||||
|
||||
Notes:
|
||||
- `NEXT_PUBLIC_TURNSTILE_SITE_KEY` is used by Next.js client-side code (the `NEXT_PUBLIC_`
|
||||
prefix is required for client exposure). TanStack Start and Nuxt also read this value.
|
||||
- In development mode, Turnstile automatically falls back to Cloudflare test keys,
|
||||
so you don't need real keys for local dev.
|
||||
|
||||
## 3. Enable Turnstile in Code
|
||||
|
||||
Edit `config/captcha.ts` in the TinyShip project:
|
||||
|
||||
```typescript
|
||||
enabled: true, // Change from false to true
|
||||
```
|
||||
|
||||
This is a code change. The agent should make this edit.
|
||||
|
||||
When `enabled: true`, Turnstile widgets appear on:
|
||||
- Login form
|
||||
- Signup form
|
||||
- Forgot password form
|
||||
- Phone login form
|
||||
- Resend verification form
|
||||
|
||||
## 4. For Workers Deployment
|
||||
|
||||
Add the secret key as a wrangler secret:
|
||||
|
||||
```bash
|
||||
cd apps/tanstack-app
|
||||
npx wrangler secret put TURNSTILE_SECRET_KEY
|
||||
```
|
||||
|
||||
The site key can go in `wrangler.jsonc` `vars` (it's public):
|
||||
|
||||
```jsonc
|
||||
"vars": {
|
||||
"NEXT_PUBLIC_TURNSTILE_SITE_KEY": "<your-site-key>"
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Verify
|
||||
|
||||
1. Start the app: `pnpm dev` (or `pnpm dev:cf`)
|
||||
2. Navigate to the login page
|
||||
3. The Turnstile widget should appear before the submit button
|
||||
4. Complete the challenge and verify login works
|
||||
|
||||
In development mode, Turnstile uses test keys that always pass — no real
|
||||
challenge is shown.
|
||||
|
||||
## How It Works
|
||||
|
||||
- **Server side**: Better Auth captcha plugin validates the Turnstile token on
|
||||
every auth request (`libs/auth/auth.ts`)
|
||||
- **Client side**: `@marsidev/react-turnstile` (Next/TanStack) or `vue-turnstile`
|
||||
(Nuxt) renders the widget in auth forms
|
||||
- **Dev mode**: Auto-fallback to Cloudflare's always-pass test keys
|
||||
@@ -0,0 +1,179 @@
|
||||
# Workers Deployment
|
||||
|
||||
## 1. Update Production URLs
|
||||
|
||||
Edit `apps/tanstack-app/wrangler.jsonc` — update `vars` with your production domain:
|
||||
|
||||
```jsonc
|
||||
"vars": {
|
||||
"APP_BASE_URL": "https://yourdomain.com",
|
||||
"BETTER_AUTH_URL": "https://yourdomain.com"
|
||||
}
|
||||
```
|
||||
|
||||
If using a custom domain, configure it in Dashboard → Workers → your worker → Custom Domains.
|
||||
Otherwise, use the default `*.workers.dev` URL.
|
||||
|
||||
## 2. Configure Secrets
|
||||
|
||||
Workers do NOT read `.env` files. All sensitive values must be set as secrets:
|
||||
|
||||
```bash
|
||||
cd apps/tanstack-app
|
||||
|
||||
# Required
|
||||
npx wrangler secret put BETTER_AUTH_SECRET
|
||||
|
||||
# If using Hyperdrive+PG (fallback connection)
|
||||
npx wrangler secret put DATABASE_URL
|
||||
|
||||
# If using R2
|
||||
npx wrangler secret put R2_ACCESS_KEY_ID
|
||||
npx wrangler secret put R2_ACCESS_KEY_SECRET
|
||||
|
||||
# If using Cloudflare Email
|
||||
npx wrangler secret put CLOUDFLARE_API_TOKEN
|
||||
|
||||
# If using Turnstile
|
||||
npx wrangler secret put TURNSTILE_SECRET_KEY
|
||||
|
||||
# Payment providers (as needed)
|
||||
# npx wrangler secret put STRIPE_SECRET_KEY
|
||||
# npx wrangler secret put STRIPE_WEBHOOK_SECRET
|
||||
|
||||
# AI providers (as needed)
|
||||
# npx wrangler secret put OPENAI_API_KEY
|
||||
```
|
||||
|
||||
**Bulk import alternative:** Create a `.secrets.json` file (do NOT commit to git):
|
||||
|
||||
```json
|
||||
{
|
||||
"BETTER_AUTH_SECRET": "...",
|
||||
"DATABASE_URL": "...",
|
||||
"R2_ACCESS_KEY_ID": "...",
|
||||
"R2_ACCESS_KEY_SECRET": "..."
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
npx wrangler secret bulk .secrets.json
|
||||
```
|
||||
|
||||
## 3. Pre-compile Email Templates
|
||||
|
||||
If using email sending, compile MJML templates before deployment:
|
||||
|
||||
```bash
|
||||
# From monorepo root
|
||||
pnpm email:compile
|
||||
```
|
||||
|
||||
MJML cannot run at runtime in Workers — templates must be pre-compiled to HTML.
|
||||
|
||||
## 4. Local Preview (Recommended)
|
||||
|
||||
Before deploying, do a production-like local test:
|
||||
|
||||
```bash
|
||||
cd apps/tanstack-app
|
||||
pnpm preview:cf
|
||||
```
|
||||
|
||||
This runs a full build + `wrangler dev`, which closely simulates the production
|
||||
Workers environment. Verify:
|
||||
|
||||
1. Home page loads
|
||||
2. Login / signup works
|
||||
3. Database queries succeed
|
||||
4. Upload works (if R2 configured)
|
||||
5. No errors in terminal
|
||||
|
||||
## 5. Deploy
|
||||
|
||||
```bash
|
||||
cd apps/tanstack-app
|
||||
pnpm run deploy:cf
|
||||
```
|
||||
|
||||
This executes `CF_DEPLOY=1 vite build && wrangler deploy`. On success, it outputs:
|
||||
|
||||
```
|
||||
https://tinyship-tanstack.<your-subdomain>.workers.dev
|
||||
```
|
||||
|
||||
## 6. Post-Deployment Verification
|
||||
|
||||
```bash
|
||||
# Health check
|
||||
curl -s -o /dev/null -w "%{http_code}" https://your-worker-url/api/health
|
||||
|
||||
# View live logs
|
||||
cd apps/tanstack-app
|
||||
npx wrangler tail
|
||||
|
||||
# Check deployment history
|
||||
npx wrangler deployments list
|
||||
|
||||
# Rollback if needed
|
||||
npx wrangler rollback
|
||||
```
|
||||
|
||||
Verify in browser:
|
||||
- Home page renders correctly
|
||||
- Login with test credentials works
|
||||
- Database-dependent features load
|
||||
|
||||
## CI/CD (Optional)
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
Create `.github/workflows/deploy-cf.yml`:
|
||||
|
||||
```yaml
|
||||
name: Deploy to Cloudflare Workers
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'apps/tanstack-app/**'
|
||||
- 'libs/**'
|
||||
- 'config/**'
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
- run: npm install -g pnpm
|
||||
- run: pnpm install
|
||||
- name: Deploy
|
||||
working-directory: apps/tanstack-app
|
||||
run: pnpm run deploy:cf
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| Secrets not available | Workers don't read `.env` — use `wrangler secret put` |
|
||||
| `Invalid hook call` during SSR | Pin `vite-tsconfig-paths` to v5.1.4 (see CF-NOTES.md) |
|
||||
| `require is not defined` | Replace `require()` with `import` in all reachable code |
|
||||
| CPU time limit exceeded | Upgrade to Workers Paid plan ($5/mo) for 30s CPU time |
|
||||
| Upload size exceeded | Free: 100MB, Paid: 500MB — use R2 direct upload for large files |
|
||||
|
||||
## Workers Plan Limits
|
||||
|
||||
| Resource | Free | Paid ($5/mo) |
|
||||
|----------|------|--------------|
|
||||
| Requests | 100k/day | 10M/mo included |
|
||||
| CPU time | 10ms | 30s |
|
||||
| Upload body | 100MB | 500MB |
|
||||
| Worker size | 3MB | 10MB |
|
||||
Reference in New Issue
Block a user