SonicJS#
一个现代的、TypeScript优先的无头CMS,专为Cloudflare边缘平台和Hono.js构建。
📦 快速开始:
npx create-sonicjs@latest my-app⚠️ 注意: 本仓库用于开发SonicJS核心包。构建应用请使用上述命令创建新项目。
🚀 主要特性#
核心平台#
- ⚡ 边缘优先:专为Cloudflare Workers设计,全球低延迟
- 🔧 开发者友好:配置优先,TypeScript优先策略
- 🤖 AI友好:结构化代码,支持AI辅助开发
- 🔌 插件系统:无需修改核心即可扩展
- 📱 现代技术栈:Hono.js、TypeScript、D1数据库、R2存储、HTMX
- 🚀 高速轻量:针对边缘计算优化
高级内容管理(阶段5)#
- 📝 集成TinyMCE的富文本编辑器,支持自定义工具栏
- 🎛️ 动态字段类型(文本、数字、日期、布尔、选择、媒体)
- 📚 完整的内容版本控制及恢复功能
- ⏰ 内容发布/撤销时间调度
- 🔄 支持基于角色权限的工作流(草稿→审核→发布→归档)
- 💾 自动保存(每30秒)
- 👁️ 实时内容预览
- 📋 内容一键复制与模板支持
- 🛡️ XSS防护,输入验证和HTML转义
🛠 技术栈#
核心框架#
- Hono.js:超快的Cloudflare Workers Web框架
- TypeScript:严格类型安全
- HTMX:增强HTML动态交互
Cloudflare服务#
- D1:边缘SQLite数据库
- R2:媒体对象存储
- Workers:无服务器计算
- KV:缓存键值存储
- Images API:图片优化与转换
开发工具#
- Vitest:快速单元测试
- Playwright:端到端测试
- Wrangler:本地开发与部署
- Drizzle ORM:类型安全数据库查询
🏁 快速开始#
快速部署SonicJS流程#
# 1.本地创建新项目
npx create-sonicjs@latest my-app
# 2.进入项目目录
cd my-app
# 3.登录cloudflare
npx wrangler login
# 4.创建 D1 数据库(记录下database_id):
npx wrangler d1 create my-sonicjs-db
# 5.创建 R2 存储桶(用于存放上传的图片/文件):
npx wrangler r2 bucket create my-sonicjs-media
# 修改配置文件 (wrangler.toml)
[[d1_databases]]
binding = "DB"
database_name = "my-sonicjs-db" # 你创建的名字
database_id = "xxxx-xxxx-xxxx" # 刚才创建后生成的 ID
[[r2_buckets]]
binding = "MEDIA_BUCKET"
bucket_name = "my-sonicjs-media" # 你创建的桶名字
**# 核心步骤:执行数据库迁移(初始化表结构)**
npm run db:migrate
# 启动开发服务器
npm run dev
# 访问 <http://localhost:8787>
**# 核心步骤:同步到远程数据库D1**
npm run db:migrate -- --remote
# 部署到cloudflare
npm run deploy备注:如遇到无法新增和保存内容,请执行以下步骤
# 初始化本地数据库
# 这一步会创建本地的 .wrangler 文件夹并生成 SQLite 数据库文件,DB必须是配置文件绑定的数据库名
npx wrangler d1 migrations apply DB --local
# 如果遇到无法新增和保存内容,可以用以下命令手动补全缺失的字段
npx wrangler d1 execute DB --local --command="ALTER TABLE content ADD COLUMN created_by TEXT;"
npx wrangler d1 execute DB --local --command="ALTER TABLE content ADD COLUMN updated_by TEXT;"
# 同步数据库表结构(核心步骤)
npx wrangler d1 migrations apply DB --remote
# 线上环境补齐
npx wrangler d1 execute DB --remote --command="ALTER TABLE content ADD COLUMN created_by TEXT;"
npx wrangler d1 execute DB --remote --command="ALTER TABLE content ADD COLUMN updated_by TEXT;"
# 再部署
npm run deploy默认包含:
- SonicJS CMS预配置
- 数据库迁移准备
- 示例内容集合
- 管理界面
/admin - 即刻部署至Cloudflare
数据库命令集#
本地数据库命令 (Local Development)#
这些命令操作的是你电脑本地 .wrangler 文件夹下的 SQLite 数据库。
| 命令 | 用途 | 备注 |
|---|---|---|
npx wrangler d1 migrations apply DB --local | 应用迁移 | 将代码中的表结构改动同步到本地数据库。 |
npx wrangler d1 execute DB --local --command="..." | 执行 SQL | 直接运行 SQL 语句,如之前补齐字段的操作。 |
npx wrangler d1 execute DB --local --file=./seed.sql | 导入数据 | 运行指定的 SQL 文件来初始化测试数据。 |
npm run db:reset | 重置数据库 | (SonicJS 特有脚本) 通常用于清空并重新初始化本地库。 |
npx wrangler d1 migrations list DB --local | 查看状态 | 查看本地有哪些迁移脚本已运行。 |
远程数据库命令 (Production/Cloudflare)#
当你准备将网站发布到线上时,需要操作 Cloudflare 云端的 D1 实例。
| 命令 | 用途 | 备注 |
|---|---|---|
npx wrangler d1 create <db-name> | 创建数据库 | 在 Cloudflare 控制台创建一个新的 D1 实例。 |
npx wrangler d1 migrations apply DB --remote | 同步表结构 | 发布必做! 将表结构同步到云端数据库。 |
npx wrangler d1 execute DB --remote --command="..." | 远程 SQL | 直接修改线上数据(慎用)。 |
npx wrangler d1 export DB --remote --output=db.sql | 导出数据 | 将线上数据备份到本地。 |
数据库迁移#
迁移文件位于 packages/core/migrations/,测试应用通过npm工作区符号链接引用。
# 查询迁移状态(本地)
wrangler d1 migrations list DB --local
# 应用本地迁移
wrangler d1 migrations apply DB --local
# 应用生产迁移
wrangler d1 migrations apply DB --remote迁移开发建议:
- 在
packages/core/migrations/新建SQL文件,命名格式如027_add_user_preferences.sql - 编写幂等SQL(CREATE TABLE IF NOT EXISTS等)
- 生成迁移包:
npm run generate:migrations - 重新构建核心包:
npm run build:core - 应用到测试数据库
📁 项目结构#
sonicjs-ai/
├── packages/
│ ├── core/ # 主CMS包,发布为 @sonicjs-cms/core
│ │ ├── src/
│ │ │ ├── routes/ # 路由处理(管理、API、认证)
│ │ │ ├── templates/ # HTML模板和组件
│ │ │ ├── middleware/# 认证与中间件
│ │ │ ├── utils/ # 工具函数
│ │ │ └── db/ # 数据库模式与迁移
│ │ └── package.json
│ ├── templates/ # 模板系统包
│ └── scripts/ # 构建脚本
├── my-sonicjs-app/ # 测试应用(git忽略)
├── www/ # 网站营销页
├── tests/e2e/ # 端到端测试
└── drizzle/ # 数据库迁移管理注意:
- 本仓库非应用项目,专注于核心包开发。
- 应用代码在
packages/core/或测试应用中。
🔧 内容管理#
创建集合#
通过管理界面或数据库定义动态字段集合。示例SQL:
INSERT INTO collections (id, name, display_name, description, schema) VALUES (
'blog-posts', 'blog_posts', '博客文章', '文章内容集合',
'{"type":"object","properties":{"title":{"type":"string","required":true}}}'
);
INSERT INTO content_fields (collection_id, field_name, field_type, field_label, field_options) VALUES
('blog-posts', 'title', 'text', '标题', '{"maxLength": 200, "required": true}'),
('blog-posts', 'content', 'richtext', '内容', '{"toolbar": "full", "height": 400}'),
('blog-posts', 'excerpt', 'text', '摘要', '{"maxLength": 500, "rows": 3}'),
('blog-posts', 'featured_image', 'media', '特色图片', '{"accept": "image/*"}'),
('blog-posts', 'publish_date', 'date', '发布日期', '{"defaultToday": true}'),
('blog-posts', 'is_featured', 'boolean', '精选文章', '{"default": false}');字段类型#
- text:单行文本,支持校验
- richtext:集成TinyMCE的富文本编辑器
- number:数字输入,支持范围限制
- boolean:复选框
- date:日期选择器
- select:单选/多选下拉
- media:文件选择及预览
🌐 API接口#
内容管理#
| 方法 | 路径 | 描述 |
|---|---|---|
| GET | /admin/content/new?collection=id | 创建新内容表单 |
| GET | /admin/content/:id/edit | 编辑内容表单 |
| POST | /admin/content/ | 创建内容(含验证) |
| PUT | /admin/content/:id | 更新内容(含版本控制) |
| DELETE | /admin/content/:id | 删除内容 |
高级功能#
| 方法 | 路径 | 描述 |
|---|---|---|
| POST | /admin/content/preview | 发布前预览内容 |
| POST | /admin/content/duplicate | 复制内容 |
| GET | /admin/content/:id/versions | 获取版本历史 |
| POST | /admin/content/:id/restore/:version | 恢复指定版本 |
| GET | /admin/content/:id/version/:version/preview | 预览历史版本 |
公共API#
| 方法 | 路径 | 描述 |
|---|---|---|
| GET | /api/content | 获取已发布内容(分页) |
| GET | /api/collections/:collection/content | 获取指定集合内容 |
| GET | /api/collections | 列出所有集合 |
🚀 部署#
环境配置示例#
# wrangler.toml
name = "my-sonicjs-app"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[[d1_databases]]
binding = "DB"
database_name = "my-app-db"
database_id = "your-database-id"
[[r2_buckets]]
binding = "MEDIA_BUCKET"
bucket_name = "my-app-media"🧪 测试#
# 运行单元测试
npm test
# 监听模式运行测试
npm run test:watch
# 端到端测试
npm run test:e2e
# 带UI的端到端测试
npm run test:e2e:ui🔌 插件开发#
通过插件扩展SonicJS功能,示例代码:
// src/plugins/my-plugin/index.ts
import { Plugin } from '@sonicjs/core'
export default {
name: 'my-plugin',
hooks: {
'content:beforeCreate': async (content) => {
// 插件逻辑
return content
}
}
} as Plugin