Jz tanstack ship
Assemble and verify a TanStack Start SaaS from a PRD.
npx -y skills add jinzhedev/jz-tanstack-shipAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 24 days oldThe repository was created 24 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Build a complete TanStack Start SaaS application from a product requirements document. 根据 PRD 组装并验证 TanStack Start SaaS;适用于“帮我搭一个 SaaS”、 “用 TanStack 建个站”、“做个带支付的网站”。Guides capability selection (Google OAuth / magic-link login, Stripe subscriptions and one-time purchases, credit ledger), assembles verified reference implementations from assets/, configures services via CLI (Stripe, Drizzle), and generates product pages from DESIGN.md. Use when the user wants to scaffold, bootstrap, or ship a new SaaS site on TanStack Start.
SKILL.md
10.9 KB, as published. Nobody here has run it
jz-tanstack-ship
Assemble a production-ready TanStack Start SaaS app. Money-handling and auth
code is copied from assets/ (verified, tested), never generated from
scratch. Product pages and UI are generated per project. Follow the phases
in order.
Phase 0 — Read inputs
- Read the user's PRD and
DESIGN.mdif provided. If neither exists, ask for a one-paragraph product description before proceeding. - Read
assets/manifest.json— it is the source of truth for capabilities, file destinations, dependencies, tables, and env vars.
Phase 1 — Capability selection
Derive defaults from the PRD, then confirm with the user (AskUserQuestion or equivalent — one question per dimension, always offer the default):
| Dimension | Options | Default |
|---|---|---|
| Database | sqlite (libsql) / postgres | sqlite |
| Deploy profile | cloudflare / vercel / node-vps | node-vps |
| Login | auth-google, auth-magic-link (multi-select) | both |
| Email provider | resend / mailgun | resend; only ask when magic-link is selected |
| Pricing model | subscriptions, onetime (multi-select) | from PRD |
| Credits | credits on/off | on if PRD mentions usage metering |
| AI generation | ai on/off + providers: replicate / fal / gemini (multi-select) | from PRD |
Enforce constraints from the manifest (e.g. credits requires a payment
capability). Record the selection as SELECTION.md in the project root —
future skill runs read it to stay consistent.
Use this machine-readable shape (Markdown headings and comments are allowed):
database: postgres
deploy: cloudflare
login: auth-google, auth-magic-link
email: resend
pricing: subscriptions, onetime
credits: on
ai: replicate, gemini
Phase 2 — Scaffold
pnpm dlx gitpick TanStack/router/tree/main/examples/react/start-basic-react-query <app-dir>
cd <app-dir>
Then remove the example's demo content:
- delete
src/routes/{posts*,users*,deferred.tsx,redirect.tsx,_pathlessLayout*},src/routes/api/users*,src/utils/{posts,users}.tsx,src/routeTree.gen.ts - edit
src/routes/__root.tsx: replace demo nav links (Posts/Users/etc.) with links to the routes you will install (/pricing,/account) — stale links breaktscbecause route types are generated.
Phase 3 — Assemble from assets
Run the deterministic assembler from the directory containing this SKILL.md:
node "$SKILL_DIR/scripts/assemble.mjs" \
--target "$APP_DIR" \
--selection "$APP_DIR/SELECTION.md"
cd "$APP_DIR"
pnpm install
Resolve SKILL_DIR and APP_DIR to absolute paths before running the command.
Do not manually copy assets or strip markers. If rerunning after generated
files were intentionally edited, review the diff first and pass --force only
when replacing those edits is intended.
The assembler performs these steps:
- Copy each
files[]entrysrc → dest. Respectwhenconditions (db:sqlite/db:postgres/ capability names). - Strip marker blocks for unselected capabilities: remove everything
between
// [cap:X]and// [/cap:X](and{/* [cap:X] */}in JSX, and# [cap:X]in env files) when X is not selected; when X is selected, remove only the marker lines. Same for[db:x]blocks — exactly one dialect survives, uncommented. Apply the same rule to[email:resend]/[email:mailgun]blocks — exactly one provider survives when magic-link is selected. Blocks nest; process inner blocks first. - Merge
deps/devDeps/dialectDeps[dialect]/dialectDevDeps[dialect]intopackage.json(scripts: addtest: vitest run,db:push: drizzle-kit push,db:generate: drizzle-kit generate,db:migrate: drizzle-kit migrate). - Generate
.envfrom the assembled.env.example; create a cryptographically randomBETTER_AUTH_SECRET. Never overwrite an existing.env.
It also writes .jz-tanstack-ship-files.json so a later selection change removes
obsolete generated files without deleting files outside the manifest. See
references/assembler.md for CLI and overwrite rules.
Files marked "ui": true are the replaceable presentation layer: if the
project has a DESIGN.md, regenerate them to match it, keeping every
authClient.* / fetch('/api/...') call and route path identical to the
reference implementation.
Phase 4 — Service configuration (CLI-first)
Do what can be done via CLI; print precise manual steps for the rest.
Database — pnpm db:push (dev). For production hand the user
db:generate + db:migrate and ask them to review SQL before applying.
For Cloudflare/Vercel PostgreSQL, use Neon with
@neondatabase/serverless WebSockets because the credit ledger uses
interactive transactions. Node/VPS uses Postgres.js. Follow
references/postgres-cloudflare-neon.md; use a dedicated database or branch in
TEST_DATABASE_URL. Never run tests against production.
Stripe (if payments selected) — see references/stripe-setup.md.
Summary: check stripe --version and login state; create products/prices for
subscription plans with stripe products create / stripe prices create;
write returned price IDs into .env (STRIPE_PRICE_*); for local dev run
stripe listen --forward-to localhost:3000/api/auth/stripe/webhook and copy
the whsec_ into .env. Production webhook endpoint:
{origin}/api/auth/stripe/webhook, events per stripeWebhookEvents in the
manifest.
Google OAuth (if selected) — use scripts/setup-google-oauth.sh to prepare
the Google Cloud project and enable required APIs, then follow
references/google-oauth-setup.md for the short manual section in Google Auth
Platform. Derive origins and redirect URIs from the actual local and production
URLs; do not guess them. Never print or commit the client secret.
Email (if magic-link selected) — dev works with no credentials (links print to the server console). For production, configure the selected provider:
- Resend: verified sender in
EMAIL_FROMand API key inRESEND_API_KEY. - Mailgun: verified sender in
EMAIL_FROM, sending domain inMAILGUN_DOMAIN, and private API key inMAILGUN_API_KEY. Mailgun uses the US API region.
Phase 5 — Product pages
Only now generate the product-specific pages (landing, feature pages, app pages) from the PRD/DESIGN.md. Rules:
- Server logic goes through the assembled modules (
~/server/credits/ledgerfor spending credits,~/catalogfor prices) — never inline new money/credit logic in routes. - To charge for a feature, call
spendCredits()in the feature's server handler and surfaceInsufficientCreditsErroras HTTP 402. - Update
src/catalog.tswith the real products from the PRD (and recreate Stripe prices to match).
Phase 6 — Verify (mandatory, in this order)
Run the selection-aware validator from the skill directory:
node "$SKILL_DIR/scripts/validate.mjs" \
--target "$APP_DIR" \
--selection "$APP_DIR/SELECTION.md"
The validator checks generated-file integrity and profile requirements, then:
- Cloudflare only: generate Worker binding types with
wrangler types. - Build first so TanStack generates
routeTree.gen.ts. - Run
tsc --noEmit. - Run database-backed Vitest tests. PostgreSQL requires
TEST_DATABASE_URL.
Use --profile-build before deployment to add wrangler deploy --dry-run for
Cloudflare or docker build for Node/VPS. Vercel's local build is already the
Nitro build above; the Vercel deployment itself requires a linked project.
Then a runtime smoke (dev server on a spare port):
GET /andGET /sign-in→ 200GET /api/creditsunauthenticated → 401- Magic-link flow (if selected): POST
/api/auth/sign-in/magic-link, extract the verify URL from the server console, GET it, confirm the session cookie thenGET /api/credits→{"balance":0} - If Stripe is configured with real test keys:
stripe trigger checkout.session.completedand confirm no error in the server log.
For the common unauthenticated checks, pass the running URL to the validator:
node "$SKILL_DIR/scripts/validate.mjs" \
--target "$APP_DIR" \
--selection "$APP_DIR/SELECTION.md" \
--structure-only \
--runtime-url http://localhost:3000
If any step fails, fix and re-run the whole phase. Do not hand off a project whose verification did not pass.
Known version pins & pitfalls
better-authand@better-auth/stripemust be the same version; better-auth ≥1.6 requiresdrizzle-orm@^0.45.2.- Stripe SDK v22 / API 2025+:
invoice.subscriptionmoved toinvoice.parent.subscription_details.subscription(already handled inrenewal-credits.ts— don't "fix" it back). - The better-auth Stripe plugin handles subscriptions only; one-time
payments intentionally go through
assets/billing/server/checkout.ts+ the plugin'sonEvent. Do not tryauthClient.subscription.upgradefor one-time SKUs. - Schema files for the two dialects must stay column-identical; when adding a table, edit both (or delete the unused one and note it in SELECTION.md).
- AI:
models.tsis the server-authoritative model catalog (alias → provider, model id, credit cost) — clients may only send aliases. Every create request returnsstatus=queuedwithout calling a provider. The client pollsGET /api/ai/tasks/$id; the first poll submits work and later polls advance it. Provider-native synchronous work such as Gemini therefore runs on the first poll, not the create request. fal and replicate adapters still need one live smoke with real keys before launch (references/ai-setup.md). - better-auth owns
user/session/account/verification/subscriptiontable shapes; after a better-auth major upgrade runpnpm dlx @better-auth/cli generateand diff againstsrc/db/schema.*.ts.
References
references/architecture.md— design rationale and data modelreferences/stripe-setup.md— exact Stripe CLI commandsreferences/google-oauth-setup.md— automated project setup and manual client checklistreferences/postgres-cloudflare-neon.md— Neon PostgreSQL on Cloudflare Workers, including testsreferences/assembler.md— deterministic assembly CLI, selection format, and overwrite rulesreferences/deploy-profiles.md— Cloudflare, Vercel, and Node/VPS runtime profilesreferences/validation.md— selection-aware validation levels and commandsreferences/extending.md— adding a new capability directory + manifest entry