种子文件是用于引导 EmDash 站点的 JSON 文档。它们定义集合、字段、分类法、菜单、重定向、小工具区域、站点设置和可选的示例内容。
根结构
{
"$schema": "https://emdashcms.com/seed.schema.json",
"version": "1",
"meta": {},
"settings": {},
"collections": [],
"taxonomies": [],
"bylines": [],
"menus": [],
"redirects": [],
"widgetAreas": [],
"sections": [],
"content": {}
}
| 字段 | 类型 | 必填 | 描述 |
|---|---|---|---|
$schema | string | 否 | 用于编辑器验证的 JSON Schema URL |
version | "1" | 是 | 种子文件格式版本 |
meta | object | 否 | 种子文件的元数据 |
settings | object | 否 | 站点设置 |
collections | array | 否 | 集合定义 |
taxonomies | array | 否 | 分类法定义 |
bylines | array | 否 | 署名档案定义 |
menus | array | 否 | 导航菜单 |
redirects | array | 否 | 重定向规则 |
widgetAreas | array | 否 | 小工具区域定义 |
sections | array | 否 | 可复用内容块 |
content | object | 否 | 示例内容条目 |
Meta
关于种子文件的可选元数据:
{
"meta": {
"name": "Blog Starter",
"description": "A simple blog with posts, pages, and categories",
"author": "EmDash"
}
}
Settings
站点全局配置值:
{
"settings": {
"title": "My Site",
"tagline": "A modern CMS",
"postsPerPage": 10,
"dateFormat": "MMMM d, yyyy"
}
}
设置以 site: 前缀应用到 options 表。设置向导允许用户覆盖 title 和 tagline。
Collections
集合定义在数据库中创建内容类型:
{
"collections": [
{
"slug": "posts",
"label": "Posts",
"labelSingular": "Post",
"description": "Blog posts",
"icon": "file-text",
"supports": ["drafts", "revisions"],
"fields": [
{
"slug": "title",
"label": "Title",
"type": "string",
"required": true
},
{
"slug": "content",
"label": "Content",
"type": "portableText"
},
{
"slug": "featured_image",
"label": "Featured Image",
"type": "image"
}
]
}
]
}
集合属性
| 属性 | 类型 | 必填 | 描述 |
|---|---|---|---|
slug | string | 是 | URL 安全标识符(小写、下划线) |
label | string | 是 | 复数显示名称 |
labelSingular | string | 否 | 单数显示名称 |
description | string | 否 | 管理面板描述 |
icon | string | 否 | Lucide 图标名称 |
supports | array | 否 | 功能特性:"drafts"、"revisions" |
fields | array | 是 | 字段定义 |
字段属性
| 属性 | 类型 | 必填 | 描述 |
|---|---|---|---|
slug | string | 是 | 列名(小写、下划线) |
label | string | 是 | 显示名称 |
type | string | 是 | 字段类型 |
required | boolean | 否 | 验证:字段必须有值 |
unique | boolean | 否 | 验证:值必须唯一 |
defaultValue | any | 否 | 新条目的默认值 |
validation | object | 否 | 附加验证规则 |
widget | string | 否 | 管理 UI 小部件覆盖 |
options | object | 否 | 小部件特定配置 |
字段类型
| 类型 | 描述 | 存储为 |
|---|---|---|
string | 短文本 | TEXT |
text | 长文本(文本区域) | TEXT |
number | 数值 | REAL |
integer | 整数 | INTEGER |
boolean | 真/假 | INTEGER |
date | 日期值 | TEXT(ISO 8601) |
datetime | 日期和时间 | TEXT(ISO 8601) |
email | 电子邮件地址 | TEXT |
url | URL | TEXT |
slug | URL 安全字符串 | TEXT |
portableText | 富文本内容 | JSON |
image | 图片引用 | JSON |
file | 文件引用 | JSON |
json | 任意 JSON | JSON |
reference | 对另一条目的引用 | TEXT |
Taxonomies
内容的分类系统:
{
"taxonomies": [
{
"name": "category",
"label": "Categories",
"labelSingular": "Category",
"hierarchical": true,
"collections": ["posts"],
"terms": [
{ "slug": "news", "label": "News" },
{ "slug": "tutorials", "label": "Tutorials" },
{
"slug": "advanced",
"label": "Advanced Tutorials",
"parent": "tutorials"
}
]
},
{
"name": "tag",
"label": "Tags",
"labelSingular": "Tag",
"hierarchical": false,
"collections": ["posts"]
}
]
}
分类法属性
| 属性 | 类型 | 必填 | 描述 |
|---|---|---|---|
name | string | 是 | 唯一标识符 |
label | string | 是 | 复数显示名称 |
labelSingular | string | 否 | 单数显示名称 |
hierarchical | boolean | 是 | 允许嵌套术语(分类目录)或扁平(标签) |
collections | array | 是 | 此分类法适用的集合 |
terms | array | 否 | 预定义术语 |
术语属性
| 属性 | 类型 | 必填 | 描述 |
|---|---|---|---|
slug | string | 是 | URL 安全标识符 |
label | string | 是 | 显示名称 |
description | string | 否 | 术语描述 |
parent | string | 否 | 父术语 slug(仅限层级结构) |
Menus
可从管理面板编辑的导航菜单:
{
"menus": [
{
"name": "primary",
"label": "Primary Navigation",
"items": [
{ "type": "custom", "label": "Home", "url": "/" },
{ "type": "page", "ref": "about" },
{ "type": "custom", "label": "Blog", "url": "/posts" },
{
"type": "custom",
"label": "External",
"url": "https://example.com",
"target": "_blank"
}
]
}
]
}
菜单项类型
| 类型 | 描述 | 必填字段 |
|---|---|---|
custom | 自定义 URL | url |
page | 链接到页面条目 | ref |
post | 链接到文章条目 | ref |
taxonomy | 链接到分类法归档 | ref、collection |
collection | 链接到集合归档 | collection |
菜单项属性
| 属性 | 类型 | 描述 |
|---|---|---|
type | string | 项目类型(见上表) |
label | string | 显示文本(page/post 引用自动生成) |
url | string | 自定义 URL(custom 类型) |
ref | string | 种子文件中的内容 ID(page/post 类型) |
collection | string | 集合 slug |
target | string | "_blank" 表示在新窗口打开 |
titleAttr | string | HTML title 属性 |
cssClasses | string | 自定义 CSS 类 |
children | array | 嵌套菜单项 |
Bylines
署名档案与所有权(author_id)分离。定义一次可复用的署名身份,然后从内容条目中引用它们。
{
"bylines": [
{
"id": "editorial",
"slug": "emdash-editorial",
"displayName": "EmDash Editorial"
},
{
"id": "guest",
"slug": "guest-contributor",
"displayName": "Guest Contributor",
"isGuest": true
}
]
}
| 属性 | 类型 | 必填 | 描述 |
|---|---|---|---|
id | string | 是 | 种子文件本地 ID,供 content[].bylines 使用 |
slug | string | 是 | URL 安全署名 slug |
displayName | string | 是 | 在模板和 API 中显示的名称 |
bio | string | 否 | 可选个人简介 |
websiteUrl | string | 否 | 可选网站 URL |
isGuest | boolean | 否 | 标记为访客署名档案 |
Redirects
用于在迁移后保留旧 URL 的重定向规则:
{
"redirects": [
{ "source": "/old-about", "destination": "/about" },
{ "source": "/legacy-feed", "destination": "/rss.xml", "type": 308 },
{
"source": "/category/news",
"destination": "/categories/news",
"groupName": "migration"
}
]
}
重定向属性
| 属性 | 类型 | 必填 | 描述 |
|---|---|---|---|
source | string | 是 | 源路径(必须以 / 开头) |
destination | string | 是 | 目标路径(必须以 / 开头) |
type | number | 否 | HTTP 状态码:301、302、307 或 308 |
enabled | boolean | 否 | 重定向是否激活(默认:true) |
groupName | string | 否 | 可选的分组标签,用于管理面板筛选/搜索 |
Widget Areas
可配置的内容区域:
{
"widgetAreas": [
{
"name": "sidebar",
"label": "Main Sidebar",
"description": "Appears on blog posts and pages",
"widgets": [
{
"type": "component",
"title": "Recent Posts",
"componentId": "core:recent-posts",
"props": { "count": 5 }
},
{
"type": "menu",
"title": "Quick Links",
"menuName": "footer"
},
{
"type": "content",
"title": "About",
"content": [
{
"_type": "block",
"style": "normal",
"children": [{ "_type": "span", "text": "Welcome to our site!" }]
}
]
}
]
}
]
}
小工具类型
| 类型 | 描述 | 必填字段 |
|---|---|---|
content | 富文本内容 | content(Portable Text) |
menu | 渲染菜单 | menuName |
component | 已注册组件 | componentId |
内置组件
| 组件 ID | 描述 |
|---|---|
core:recent-posts | 最新文章列表 |
core:categories | 分类目录列表 |
core:tags | 标签云 |
core:search | 搜索表单 |
core:archives | 月度归档 |
Sections
编辑器可通过 /section 斜杠命令插入到 Portable Text 字段中的可复用内容块:
{
"sections": [
{
"slug": "hero-centered",
"title": "Centered Hero",
"description": "Full-width hero with centered heading and CTA button",
"keywords": ["hero", "banner", "header", "landing"],
"content": [
{
"_type": "block",
"style": "h1",
"children": [{ "_type": "span", "text": "Welcome to Our Site" }]
},
{
"_type": "block",
"children": [
{ "_type": "span", "text": "Your compelling tagline goes here." }
]
}
]
}
]
}
区块属性
| 属性 | 类型 | 必填 | 描述 |
|---|---|---|---|
slug | string | 是 | URL 安全标识符 |
title | string | 是 | 在区块选择器中显示的名称 |
description | string | 否 | 说明何时使用此区块 |
keywords | array | 否 | 用于查找区块的搜索词 |
content | array | 是 | Portable Text 块 |
source | string | 否 | "theme"(种子文件默认值)或 "import" |
来自种子文件的区块标记为 source: "theme",不能从管理 UI 删除。编辑者可以创建自己的区块(source: "user"),并在编辑内容时插入任何区块类型。
Content
按集合组织的示例内容:
{
"content": {
"posts": [
{
"id": "hello-world",
"slug": "hello-world",
"status": "published",
"bylines": [
{ "byline": "editorial" },
{ "byline": "guest", "roleLabel": "Guest essay" }
],
"data": {
"title": "Hello World",
"content": [
{
"_type": "block",
"style": "normal",
"children": [{ "_type": "span", "text": "Welcome!" }]
}
],
"excerpt": "Your first post."
},
"taxonomies": {
"category": ["news"],
"tag": ["welcome", "first-post"]
}
}
],
"pages": [
{
"id": "about",
"slug": "about",
"status": "published",
"data": {
"title": "About Us",
"content": [
{
"_type": "block",
"style": "normal",
"children": [{ "_type": "span", "text": "About page content." }]
}
]
}
}
]
}
}
内容条目属性
| 属性 | 类型 | 必填 | 描述 |
|---|---|---|---|
id | string | 是 | 用于引用的种子文件本地 ID |
slug | string | 是 | URL slug |
status | string | 否 | "published" 或 "draft"(默认:"published") |
data | object | 是 | 字段值 |
bylines | array | 否 | 有序署名信息(byline,可选 roleLabel) |
taxonomies | object | 否 | 按分类法名称分配的术语 |
内容引用
使用 $ref: 前缀引用其他内容条目:
{
"data": {
"related_posts": ["$ref:another-post", "$ref:third-post"]
}
}
$ref: 前缀在播种过程中将种子 ID 解析为数据库 ID。
媒体引用
从 URL 包含图片:
{
"data": {
"featured_image": {
"$media": {
"url": "https://images.unsplash.com/photo-xxx",
"alt": "Description of the image",
"filename": "hero.jpg",
"caption": "Photo by Someone"
}
}
}
}
从 .emdash/media/ 包含本地图片:
{
"data": {
"featured_image": {
"$media": {
"file": "hero.jpg",
"alt": "Description of the image"
}
}
}
}
媒体属性
| 属性 | 类型 | 必填 | 描述 |
|---|---|---|---|
url | string | 是* | 要下载的远程 URL |
file | string | 是* | .emdash/media/ 中的本地文件名 |
alt | string | 否 | 无障碍替代文本 |
filename | string | 否 | 覆盖文件名 |
caption | string | 否 | 媒体说明 |
*url 或 file 二者必填其一,不能同时使用。
编程方式应用种子文件
使用种子 API 进行 CLI 工具或脚本操作:
import { applySeed, validateSeed } from "emdash/seed";
import seedData from "./.emdash/seed.json";
const validation = validateSeed(seedData);
if (!validation.valid) {
console.error(validation.errors);
process.exit(1);
}
const result = await applySeed(db, seedData, {
includeContent: true,
onConflict: "skip",
storage: myStorage,
baseUrl: "http://localhost:4321",
});
console.log(result);
// {
// collections: { created: 2, skipped: 0 },
// fields: { created: 8, skipped: 0 },
// taxonomies: { created: 2, terms: 5 },
// bylines: { created: 2, skipped: 0 },
// menus: { created: 1, items: 4 },
// redirects: { created: 3, skipped: 0 },
// widgetAreas: { created: 1, widgets: 3 },
// settings: { applied: 3 },
// content: { created: 3, skipped: 0 },
// media: { created: 2, skipped: 0 }
// }
应用选项
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
includeContent | boolean | false | 创建示例内容条目 |
onConflict | string | "skip" | "skip"、"update" 或 "error" |
mediaBasePath | string | — | 本地媒体文件的基础路径 |
storage | Storage | — | 用于媒体上传的存储适配器 |
baseUrl | string | — | 媒体 URL 的基础 URL |
幂等性
播种可以安全地多次运行。各实体类型的冲突行为:
| 实体 | 行为 |
|---|---|
| 集合 | 如果 slug 已存在则跳过 |
| 字段 | 如果集合 + slug 已存在则跳过 |
| 分类法定义 | 如果名称已存在则跳过 |
| 分类法术语 | 如果名称 + slug 已存在则跳过 |
| 署名档案 | 如果 slug 已存在则跳过 |
| 菜单 | 如果名称已存在则跳过 |
| 菜单项 | 全部替换(菜单会被重新创建) |
| 重定向 | 如果源路径已存在则跳过 |
| 小工具区域 | 如果名称已存在则跳过 |
| 小工具 | 全部替换(区域会被重新创建) |
| 区块 | 如果 slug 已存在则跳过 |
| 设置 | 更新(设置本身就是可变的) |
| 内容 | 如果集合中 slug 已存在则跳过 |
验证
种子文件在应用之前会进行验证:
import { validateSeed } from "emdash/seed";
const { valid, errors, warnings } = validateSeed(seedData);
if (!valid) {
errors.forEach((e) => console.error(e));
}
warnings.forEach((w) => console.warn(w));
验证检查包括:
- 必填字段是否存在
- Slug 是否遵循命名规范(小写、下划线)
- 字段类型是否有效
- 引用是否指向现有内容
- 层级术语的父级是否存在
- 重定向路径是否为安全的本地 URL
- 重定向源是否唯一
- 集合内是否有重复的 slug
CLI 命令
# 应用种子文件
npx emdash seed .emdash/seed.json
# 不包含示例内容应用
npx emdash seed .emdash/seed.json --no-content
# 仅验证
npx emdash seed .emdash/seed.json --validate
# 将当前 Schema 导出为种子文件
npx emdash export-seed > seed.json
# 导出包含内容
npx emdash export-seed --with-content > seed.json