Scaffold
Skill TNT-Likely/honeycomb/plugins/fastapi-vite-saas/skills/scaffold
当用户要起一个 FastAPI 后端 + Vite SPA 前端 + Docker + GitHub Actions 的单仓双端 SaaS 项目时使用。一个 docker run 就跑起来的自托管 SaaS / 内部工具骨架,脱胎于 BeeCount-Cloud 生产实践。触发关键词:"scaffold FastAPI + Vite 项目"、"新建一个 SaaS 单仓"、"起一个 FastAPI Docker monorepo"、"按 fastapi-vite-saas / BeeCount-Cloud 风格建项目"、"create a fastapi vite saas template"、"new selfhost monorepo with FastAPI and Vite"。不适用于纯前端项目、纯 CLI 工具、纯库项目。From its SKILL.md
npx -y skills add TNT-Likely/honeycomb --skill scaffoldAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 2 stars2 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.
- runs commandsInstructs the agent to run 6 commands, including `git init && git add -A && git commit -m "feat: fastapi-vite-saas 脚手架初始化"` and 5 more.
SKILL.md
13.6 KB, ~4.4k tokens by cl100k_base, as published. Nobody here has run it
FastAPI + Vite SaaS 脚手架技能
这套架构是什么
一个单仓双端的全栈应用骨架:Python 后端 + 前端 SPA 共住一个 git 仓库,通过多阶段 Docker 构建打成单一镜像,GitHub Actions 自动发版。核心定位:用一个 docker run 就能跑起来的自托管 SaaS。
技术选型(每一项都解释为什么):
| 层 | 选型 | 为什么 |
|---|---|---|
| Web framework | FastAPI | Pydantic v2 校验直接当 schema、async 原生、OpenAPI 自动生成 |
| ORM | SQLAlchemy 2.x | 久经考验、async 支持成熟、Alembic 配套迁移成熟 |
| Migrations | Alembic | 不要自己搞 schema_migrations 表 |
| Auth | JWT(PyJWT)+ passlib + 可选 pyotp 2FA | 无状态、单镜像无 redis 依赖 |
| 调度 | APScheduler BackgroundScheduler | in-process,不引入额外服务;线程池避免阻塞事件循环 |
| 备份加密 | pyzipper(AES-256 zip) | stdlib zipfile 的 ZipCrypto 已破,pyzipper 是 drop-in 替换 |
| 对象存储 | rclone subprocess | 一个二进制覆盖 S3/R2/WebDAV/B2/GDrive/OneDrive |
| Lint/Type/Test | ruff + mypy + pytest | 标配,启动快 |
| Frontend | pnpm workspace + Vite + (React/Vue 任选) | apps/* + packages/* 拆分清晰 |
| 构建产物 | 单一 Docker 镜像 | 多阶段:Node build 前端 → Python 运行时拷 dist/ 当静态 |
| CI | GitHub Actions | ci.yml(test/lint/build)+ release.yml(tag 触发 → docker hub) |
| 编排 | Makefile | 不引入 just/task,Makefile 一份所有人都会读 |
仓库布局(基线)
<project>/
├── .github/
│ └── workflows/
│ ├── ci.yml # PR/push: ruff + mypy + pytest + frontend build
│ └── release.yml # tag 'v*': build & push docker image
├── .docs/ # 内部设计文档(不发布)
├── docs/ # 用户向文档(随发版打入镜像/网站)
├── alembic/
│ ├── env.py
│ ├── script.py.mako
│ └── versions/
├── data/ # 运行时数据(gitignored,.gitkeep 占位)
│ └── .gitkeep
├── frontend/
│ ├── pnpm-workspace.yaml
│ ├── package.json # 仅 workspace 根
│ ├── apps/
│ │ └── web/ # Vite SPA
│ └── packages/
│ ├── api-client/ # 类型化 API client(从 OpenAPI 生成)
│ ├── ui/ # 共享组件
│ └── features/ # 跨页可复用业务组件
├── scripts/ # 运维脚本(seed/backup/grant-admin/...)
├── src/ # Python package(直接 import,不再嵌一层)
│ ├── __init__.py
│ ├── main.py # FastAPI app + lifespan
│ ├── config.py # pydantic-settings,所有 env 集中定义
│ ├── database.py # engine / session / Base
│ ├── deps.py # FastAPI Depends 工厂(get_db / get_user / ...)
│ ├── models.py # SQLAlchemy ORM(小项目放一起,大了再拆)
│ ├── schemas.py # Pydantic schemas(同上)
│ ├── security.py # 密码哈希 + JWT 签发/校验 + 可选 TOTP
│ ├── error_handling.py # 全局异常 → JSON 响应映射
│ ├── observability.py # structured logging + metrics
│ └── routers/
│ └── <group>/
│ ├── __init__.py # 聚合 APIRouter,main.py import 不变
│ ├── _shared.py # 共享 imports/helpers/router 实例;__all__ 显式
│ └── <entity>.py # 单一资源的 POST/PATCH/DELETE 或一组 GET
├── tests/ # pytest,与 src/ 对称
├── .env.example # 所有 env 的注释样本(.env 进 gitignore)
├── .gitignore
├── alembic.ini
├── docker-compose.yml # 生产 compose(用户拿去 deploy)
├── docker-compose.postgres.yml # overlay,可选切换 Postgres
├── Dockerfile # 多阶段:frontend-builder → python:3.12-slim
├── Makefile # 唯一的命令编排入口
├── mypy.ini
├── pytest.ini
├── README.md / README.en.md # 双语
├── requirements.txt # 不用 pyproject — 简单项目 requirements 更直白
├── ruff.toml
└── server.py # 极简 entry:from src.main import app
几个不显眼但关键的设计:
server.py在仓根:让uvicorn server:app能直接跑。生产 Dockerfile 也用这个,本地 dev 也用这个,统一入口避免本地/生产两套启动方式。src/不再嵌套包名:小项目里<project>/src/<project>/foo.py是没必要的间接层。直接src/foo.py,Python path 设.(PYTHONPATH=.)即可。- 路由用包不用文件:每个
routers/<group>/是包,_shared.py集中共享导入和 router 实例。新增 endpoint 只动单个 entity 文件,改共享逻辑只动_shared.py。不要把业务塞进__init__.py,它只做聚合。 /data是唯一持久化目录:DB、备份、附件、用户上传 — 全部走DATA_DIRenv,默认/data(docker)或./data(本地)。容器部署只挂一个 volume 就能整体备份。.docs/vsdocs/:点前缀的不发布(内部设计/复盘),无点前缀的对外。明确分流。
用户工作流(Makefile 是核心)
scaffold 完成后,用户应该能直接:
make setup-backend # 建 venv + pip install + 拷 .env.example → .env
make migrate # alembic upgrade head
make seed-demo # 灌种子数据(可选)
make dev-api # uvicorn --reload --host 0.0.0.0
make dev-web # pnpm install + vite dev
make dev-db # docker compose up postgres(如果切到 PG)
make test # pytest
make lint # ruff check
make typecheck # mypy src
make wipe-local # 清本地 dev 数据(保留 .gitkeep 和指定的构建产物)
dev-api 必须 --host 0.0.0.0:模拟器/真机经 WiFi 用 IP 访问时,uvicorn 默认绑 127.0.0.1 会 Connection refused。这条踩过,scaffold 时直接写好。
scaffold 流程(AI 执行步骤)
当用户说"按 fastapi-vite-saas 起一个新项目叫 Foo"时,按以下顺序执行:
Step 1: 收集参数
如果用户没给全,问清楚(一次性问,不要来回往返):
- 项目名(kebab-case,如
foo-cloud)→ 同时确定 Python 包名(snake_case)、Docker image 名、env 前缀 - 目标路径(默认
~/code/mine/<project-name>) - 前端框架:React / Vue / Svelte(默认 React,跟 BeeCount-Cloud 一致)
- 默认数据库:SQLite(单镜像零依赖)/ Postgres(团队/生产强一致需求)— 默认 SQLite,Postgres 通过 overlay compose 切换
- 是否需要 2FA(默认否,简化首版)
- 是否需要备份模块(rclone + 加密 zip,默认否,有需要再加)
- GitHub owner(用户名/组织名,写进 Dockerfile/README/CI)
Step 2: 创建目录树 + 生成基础文件
按上面的"仓库布局"创建空目录(包括 .gitkeep 占位)。
然后生成以下文件,所有 {{project_name}} / {{python_pkg}} / {{github_owner}} / {{image_name}} 替换成 Step 1 参数:
requirements.txt— 基线依赖(从这个 skill 同目录的templates/requirements.txt拷,见下面"模板文件")src/main.py— FastAPI app 骨架,挂 CORS、健康检查/healthz、版本端点/api/v1/versionsrc/config.py— pydantic-settings,声明所有 env(DATABASE_URL, JWT_SECRET, DATA_DIR, CORS_ORIGINS, APP_ENV, APP_VERSION)src/database.py— engine(根据 DATABASE_URL 自动判 sqlite/pg)+ SessionLocal + Basesrc/deps.py— get_db / get_current_user 占位src/security.py— passlib CryptContext + JWT encode/decodesrc/models.py— User 模型占位src/schemas.py— UserCreate/UserRead/Token 占位src/error_handling.py— 把常见异常映射成 JSON(401/403/404/422/500)src/routers/auth/__init__.py+_shared.py+register.py+login.py— 完整可跑的注册登录端点alembic.ini+alembic/env.py+ 第一个 migration(创建 users 表)server.py— 一行:from src.main import appMakefile— 全套 dev/test/lint/typecheck/wipe-localDockerfile— 多阶段构建(参考 BeeCount-Cloud,删掉 docs-index 那一段,普通项目不需要 RAG 索引)docker-compose.yml— 单服务,挂./data:/data,暴露端口可参数化(默认 8080 → host)docker-compose.postgres.yml— overlay,定义dbservice + 覆盖DATABASE_URL.env.example— 所有 env 的注释样本,JWT_SECRET 写change-me-32-bytes-strong-random.gitignore— Python + Node + venv + .env + data/* (保留 .gitkeep) + IDEruff.toml/mypy.ini/pytest.ini— 直接抄 BeeCount-Cloud 的配置.github/workflows/ci.yml— matrix: backend(ruff + mypy + pytest)+ frontend(pnpm install + build).github/workflows/release.yml— tag 触发,build docker → push docker hub(用{{github_owner}}/{{image_name}})README.md+README.en.md— 模板文档(项目简介、quickstart、deploy、env 说明)frontend/pnpm-workspace.yaml+frontend/package.jsonfrontend/apps/web/— Vite + React/Vue 模板(用pnpm create vite思路,生成最小可跑)frontend/packages/api-client/— 占位 + 注释说明"从 OpenAPI 用 openapi-typescript 生成"frontend/packages/ui/+frontend/packages/features/— 占位 + 一个 hello 组件
Step 3: 初始化 git + 装依赖 + 跑一次自检
cd <target>
git init && git add -A && git commit -m "feat: fastapi-vite-saas 脚手架初始化"
make setup-backend
make migrate
make test # 期望:0 个测试,exit 0
cd frontend && pnpm install
pnpm -C apps/web build # 期望:dist/ 产出
任何一步失败,立即停下报错给用户,不要试图绕过。
Step 4: 提示下一步
最后给用户一份"接下来要干的 5 件事"清单:
- 改
.env.example的JWT_SECRET为真随机串(给一行openssl rand -hex 32命令) - 把仓库推到 GitHub:
gh repo create {{github_owner}}/{{project_name}} --public --source . --push - 配 Docker Hub secrets(
DOCKERHUB_USERNAME/DOCKERHUB_TOKEN)让 release.yml 能 push 镜像 - 改 README 里的 placeholder(项目简介、screenshot)
- 起第一个业务实体:跟 AI 说"加一个 <实体名> 资源",AI 应该按 routers/<group>/ + models.py + schemas.py + alembic migration 这套流程加
不要从 BeeCount-Cloud 抄过来的部分
以下是 BeeCount-Cloud 的领域逻辑,新项目里不要默认带上:
sync_changes+read_*_projection+sync_applier.py那套多端同步架构(只在需要"移动端离线 + 多设备同步"时才搭)- AI 文档 Q&A / RAG /
numpycosine search /docs-index.*.sqlite(只在产品本身要做文档搜索时才搭) mcp/目录 MCP server(只在要给 LLM 工具调用时才搭)pyzipper/rclone/apscheduler备份链(在用户明确说要做"自助备份到对象存储"时才搭)pyotp/ TOTP 2FA(只在 step 1 用户明确说要 2FA 时才搭)openpyxlExcel 导入(域特定,不要默认装)
每一项都增加运行时复杂度。默认 scaffold 是最小可跑的 FastAPI + Vite + Docker,其它当 add-on 按需启用。
模板文件位置
与 SKILL.md 同目录,有一个 templates/ 子目录,里面是上面 Step 2 列出的所有文件的实际模板内容(用 {{var}} 占位)。AI 执行时:
- 读
templates/<filename>内容 - 用 Step 1 收集的参数做字符串替换
- 写到
<target>/<filename>
模板暂未填充时,AI 可以基于 BeeCount-Cloud 对应文件现场改写(把 beecount / BeeCount / 8869 / sunxiao0721 等字面量替换成新项目参数)。后续版本会把模板固化进 templates/,减少现场改写的随机性。
反模式(已踩过的坑)
- ❌ 不要在
server.py里写业务,它只能是from src.main import app一行 — 否则本地/生产入口分裂 - ❌ 不要用硬编码 SQL
CREATE TABLE ... AS SELECT做迁移,全走 Alembic - ❌ 不要在 Drift/Alembic 之外维护自己的
schema_migrations表 - ❌ Dockerfile 里不要在 frontend builder 阶段就
COPY frontend/全量 — 先只拷package.json+ lock,装完依赖再 COPY 全量,Docker layer cache 才能命中 - ❌ 不要把
data/留在仓里跟构建产物混(docs-index.*.sqlite是个例外:它是从兄弟仓 build 出来 commit 进去的索引) - ❌ HEALTHCHECK 不要打
/api/v1/healthz,挂在根/healthz,避免被前缀路由拦掉
升级与变体
- 切 Postgres:
docker-compose -f docker-compose.yml -f docker-compose.postgres.yml up,.env改DATABASE_URL=postgresql+psycopg://... - 切 Vue/Svelte:Step 1 选项分支,生成
frontend/apps/web/时换模板 - 加 MCP server:用
mcp>=1.27.0装 FastMCP,挂在/api/v1/mcp,PAT 鉴权 - 加备份模块:装 pyzipper + apscheduler,起一个
src/backup/包,scripts/backup_sqlite.sh兜底 CLI
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.